diff --git a/app/components/school/new-vocabulary-form.gjs b/app/components/school/new-vocabulary-form.gjs index a55767efb9..033e5a45b9 100644 --- a/app/components/school/new-vocabulary-form.gjs +++ b/app/components/school/new-vocabulary-form.gjs @@ -8,6 +8,7 @@ import { TrackedAsyncData } from 'ember-async-data'; import { uniqueId } from '@ember/helper'; import t from 'ember-intl/helpers/t'; import { on } from '@ember/modifier'; +import focus from '../../modifiers/focus'; import pick from '../../helpers/pick'; import set from 'ember-set-helper/helpers/set'; import perform from 'ember-concurrency/helpers/perform'; @@ -16,7 +17,6 @@ import LoadingSpinner from '../loading-spinner'; export default class SchoolNewVocabularyFormComponent extends Component { @service intl; - @service store; @tracked title; validations = new YupValidations(this, { @@ -47,20 +47,20 @@ export default class SchoolNewVocabularyFormComponent extends Component { return this.schoolVocabulariesData.value.map(({ title }) => title); } - saveNew = task({ drop: true }, async () => { + save = task({ drop: true }, async () => { this.validations.addErrorDisplayForAllFields(); const isValid = await this.validations.isValid(); if (!isValid) { return false; } - await this.args.save.linked().perform(this.title, this.args.school, true); + await this.args.save(this.title, this.args.school, true); this.validations.clearErrorDisplay(); }); saveOrCancel = task({ drop: true }, async (event) => { const keyCode = event.keyCode; if (13 === keyCode) { - await this.saveNew.perform(); + await this.save.perform(); return; } if (27 === keyCode) { @@ -70,6 +70,9 @@ export default class SchoolNewVocabularyFormComponent extends Component { , ); - assert.strictEqual(component.title, `Title: ${vocabulary.title} (0 total)`); + assert.strictEqual(component.vocabularyTitle.text, this.vocabulary.title); assert.notOk(component.hasError); - await component.editTitle(); - await component.changeTitle(''); - await component.saveTitle(); + await component.vocabularyTitle.edit(); + await component.vocabularyTitle.change(''); + await component.vocabularyTitle.save(); assert.ok(component.hasError); assert.strictEqual(component.error, 'Title can not be blank'); assert.strictEqual(this.server.db.vocabulary.all()[0].title, 'Vocabulary 1'); }); test('validation fails if vocabulary title is too long', async function (assert) { - const school = await this.server.create('school'); - const vocabulary = await this.server.create('vocabulary', { school }); const vocabularyModel = await this.owner .lookup('service:store') - .findRecord('vocabulary', vocabulary.id); + .findRecord('vocabulary', this.vocabulary.id); this.set('vocabulary', vocabularyModel); await render( @@ -147,26 +202,24 @@ module('Integration | Component | school/vocabulary-manager', function (hooks) { /> , ); - assert.strictEqual(component.title, `Title: ${vocabulary.title} (0 total)`); + assert.strictEqual(component.vocabularyTitle.text, this.vocabulary.title); assert.notOk(component.hasError); - await component.editTitle(); - await component.changeTitle('a'.repeat(201)); - await component.saveTitle(); + await component.vocabularyTitle.edit(); + await component.vocabularyTitle.change('a'.repeat(201)); + await component.vocabularyTitle.save(); assert.ok(component.hasError); assert.strictEqual(component.error, 'Title is too long (maximum is 200 characters)'); assert.strictEqual(this.server.db.vocabulary.all()[0].title, 'Vocabulary 1'); }); test('prevent duplicate vocabulary title', async function (assert) { - const school = await this.server.create('school'); - const vocabulary = await this.server.create('vocabulary', { school }); await this.server.create('vocabulary', { - school, + school: this.school, title: 'duplicate one', }); const vocabularyModel = await this.owner .lookup('service:store') - .findRecord('vocabulary', vocabulary.id); + .findRecord('vocabulary', this.vocabulary.id); this.set('vocabulary', vocabularyModel); await render( @@ -179,22 +232,20 @@ module('Integration | Component | school/vocabulary-manager', function (hooks) { /> , ); - assert.strictEqual(component.title, `Title: ${vocabulary.title} (0 total)`); + assert.strictEqual(component.vocabularyTitle.text, this.vocabulary.title); assert.notOk(component.hasError); - await component.editTitle(); - await component.changeTitle('duplicate one'); - await component.saveTitle(); + await component.vocabularyTitle.edit(); + await component.vocabularyTitle.change('duplicate one'); + await component.vocabularyTitle.save(); assert.ok(component.hasError); assert.strictEqual(component.error, 'Title is a duplicate'); assert.strictEqual(this.server.db.vocabulary.all()[0].title, 'Vocabulary 1'); }); test('add term', async function (assert) { - const school = await this.server.create('school'); - const vocabulary = await this.server.create('vocabulary', { school }); const vocabularyModel = await this.owner .lookup('service:store') - .findRecord('vocabulary', vocabulary.id); + .findRecord('vocabulary', this.vocabulary.id); this.set('vocabulary', vocabularyModel); await render( @@ -207,18 +258,21 @@ module('Integration | Component | school/vocabulary-manager', function (hooks) { /> , ); - assert.strictEqual(component.title, `Title: ${vocabulary.title} (0 total)`); - assert.strictEqual(component.terms.list.length, 0); + assert.strictEqual(component.vocabularyTitle.text, this.vocabulary.title); + assert.strictEqual(component.vocabularyTermsTitle.text, 'Terms (0 total)'); + assert.strictEqual(component.terms.termsList.items.length, 0); - await component.terms.newTermForm.setTitle('new term'); - await component.terms.newTermForm.save(); - assert.strictEqual(component.terms.list.length, 1, 'term count correct'); + await component.toggleNewVocabularyTermForm(); + await component.newTermForm.setTitle('new term'); + await component.newTermForm.save(); + assert.strictEqual(component.vocabularyTermsTitle.text, 'Terms (1 total)'); + assert.strictEqual(component.terms.termsList.items.length, 1, 'term count correct'); assert.strictEqual(this.server.db.term.all()[0].title, 'new term'); - assert.strictEqual(this.server.db.term.all()[0].id, vocabulary.id); + assert.strictEqual(this.server.db.term.all()[0].id, Number(this.vocabulary.id)); }); - test('cant add term with empty title', async function (assert) { + test('cannot add term with empty title', async function (assert) { const school = await this.server.create('school'); const vocabulary = await this.server.create('vocabulary', { school }); const vocabularyModel = await this.owner @@ -236,26 +290,28 @@ module('Integration | Component | school/vocabulary-manager', function (hooks) { /> , ); - assert.strictEqual(component.title, `Title: ${vocabulary.title} (0 total)`); - assert.strictEqual(component.terms.list.length, 0); - - assert.notOk(component.terms.newTermForm.hasError); - await component.terms.newTermForm.setTitle(''); - await component.terms.newTermForm.save(); - assert.ok(component.terms.newTermForm.hasError); - assert.strictEqual(component.terms.newTermForm.errorMessage, 'Term can not be blank'); - assert.strictEqual(component.terms.list.length, 0); + assert.strictEqual(component.vocabularyTitle.text, vocabulary.title); + assert.strictEqual(component.vocabularyTermsTitle.text, 'Terms (0 total)'); + assert.strictEqual(component.terms.termsList.items.length, 0); + + assert.notOk(component.newTermForm.hasError); + + await component.toggleNewVocabularyTermForm(); + await component.newTermForm.setTitle(''); + await component.newTermForm.save(); + assert.ok(component.newTermForm.hasError); + assert.strictEqual(component.newTermForm.errorMessage, 'Term can not be blank'); + assert.strictEqual(component.vocabularyTermsTitle.text, 'Terms (0 total)'); + assert.strictEqual(component.terms.termsList.items.length, 0); }); - test('cant add term with duplicate title', async function (assert) { - const school = await this.server.create('school'); - const vocabulary = await this.server.create('vocabulary', { school }); + test('cannot add term with duplicate title', async function (assert) { await this.server.create('term', { - vocabulary, + vocabulary: this.vocabulary, }); const vocabularyModel = await this.owner .lookup('service:store') - .findRecord('vocabulary', vocabulary.id); + .findRecord('vocabulary', this.vocabulary.id); this.set('vocabulary', vocabularyModel); await render( @@ -268,14 +324,18 @@ module('Integration | Component | school/vocabulary-manager', function (hooks) { /> , ); - assert.strictEqual(component.title, `Title: ${vocabulary.title} (1 total)`); - assert.strictEqual(component.terms.list.length, 1); - - assert.notOk(component.terms.newTermForm.hasError); - await component.terms.newTermForm.setTitle('term 0'); - await component.terms.newTermForm.save(); - assert.ok(component.terms.newTermForm.hasError); - assert.strictEqual(component.terms.newTermForm.errorMessage, 'Term is a duplicate'); - assert.strictEqual(component.terms.list.length, 1); + assert.strictEqual(component.vocabularyTitle.text, this.vocabulary.title); + assert.strictEqual(component.vocabularyTermsTitle.text, 'Terms (1 total)'); + assert.strictEqual(component.terms.termsList.items.length, 1); + + assert.notOk(component.newTermForm.hasError); + + await component.toggleNewVocabularyTermForm(); + await component.newTermForm.setTitle('term 0'); + await component.newTermForm.save(); + assert.ok(component.newTermForm.hasError); + assert.strictEqual(component.newTermForm.errorMessage, 'Term is a duplicate'); + assert.strictEqual(component.vocabularyTermsTitle.text, 'Terms (1 total)'); + assert.strictEqual(component.terms.termsList.items.length, 1); }); }); diff --git a/tests/integration/components/school/vocabulary-new-term-test.gjs b/tests/integration/components/school/vocabulary-new-term-test.gjs index 405c65160d..da3f323926 100644 --- a/tests/integration/components/school/vocabulary-new-term-test.gjs +++ b/tests/integration/components/school/vocabulary-new-term-test.gjs @@ -10,6 +10,36 @@ module('Integration | Component | school/vocabulary-new-term', function (hooks) setupRenderingTest(hooks); setupMSW(hooks); + test('it renders', async function (assert) { + const school = await this.server.create('school'); + const vocabulary = await this.server.create('vocabulary', { school }); + const vocabularyModel = await this.owner + .lookup('service:store') + .findRecord('vocabulary', vocabulary.id); + const newTitle = 'new term'; + const newDescription = 'a description'; + + this.set('vocabulary', vocabularyModel); + this.set('createTerm', (title, description, isActive) => { + assert.step('createTerm called'); + assert.strictEqual(title, newTitle); + assert.strictEqual(description, newDescription); + assert.true(isActive); + }); + await render( + , + ); + + assert.strictEqual(component.title, 'New Term'); + assert.strictEqual(component.isActive.yesNoToggle.checked, 'true'); + }); + test('add term', async function (assert) { const school = await this.server.create('school'); const vocabulary = await this.server.create('vocabulary', { school }); @@ -17,24 +47,82 @@ module('Integration | Component | school/vocabulary-new-term', function (hooks) .lookup('service:store') .findRecord('vocabulary', vocabulary.id); const newTitle = 'new term'; + const newDescription = 'a description'; + + this.set('vocabulary', vocabularyModel); + this.set('createTerm', (title, description, isActive) => { + assert.step('createTerm called'); + assert.strictEqual(title, newTitle); + assert.strictEqual(description, newDescription); + assert.true(isActive); + }); + await render( + , + ); + + await component.setTitle(newTitle); + await component.setDescription(newDescription); + await component.save(); + assert.verifySteps(['createTerm called']); + }); + + test('active defaults to true', async function (assert) { + const school = await this.server.create('school'); + const vocabulary = await this.server.create('vocabulary', { school }); + const vocabularyModel = await this.owner + .lookup('service:store') + .findRecord('vocabulary', vocabulary.id); + + this.set('vocabulary', vocabularyModel); + await render( + , + ); + + assert.strictEqual(component.isActive.yesNoToggle.checked, 'true'); + }); + + test('toggle active', async function (assert) { + const school = await this.server.create('school'); + const vocabulary = await this.server.create('vocabulary', { school }); + const vocabularyModel = await this.owner + .lookup('service:store') + .findRecord('vocabulary', vocabulary.id); + const newTitle = 'new term'; this.set('vocabulary', vocabularyModel); - this.set('createTerm', (title) => { + this.set('createTerm', (title, description, isActive) => { assert.step('createTerm called'); - assert.strictEqual(newTitle, title); + assert.false(isActive); }); await render( , ); + await component.isActive.yesNoToggle.handle.click(); await component.setTitle(newTitle); await component.save(); assert.verifySteps(['createTerm called']); }); - test("can't add term with empty title", async function (assert) { + test('cannot add term with empty title', async function (assert) { const school = await this.server.create('school'); const vocabulary = await this.server.create('vocabulary', { school }); const vocabularyModel = await this.owner @@ -44,7 +132,11 @@ module('Integration | Component | school/vocabulary-new-term', function (hooks) this.set('vocabulary', vocabularyModel); await render( , ); @@ -55,7 +147,7 @@ module('Integration | Component | school/vocabulary-new-term', function (hooks) assert.strictEqual(component.errorMessage, 'Term can not be blank'); }); - test("can't add term with long title", async function (assert) { + test('cannot add term with long title', async function (assert) { const school = await this.server.create('school'); const vocabulary = await this.server.create('vocabulary', { school }); const vocabularyModel = await this.owner @@ -65,7 +157,11 @@ module('Integration | Component | school/vocabulary-new-term', function (hooks) this.set('vocabulary', vocabularyModel); await render( , ); @@ -76,7 +172,7 @@ module('Integration | Component | school/vocabulary-new-term', function (hooks) assert.strictEqual(component.errorMessage, 'Term is too long (maximum is 200 characters)'); }); - test("can't add top-level term with duplicate title", async function (assert) { + test('cannot add top-level term with duplicate title', async function (assert) { const title = 'Aardvark'; const school = await this.server.create('school'); const vocabulary = await this.server.create('vocabulary', { school }); @@ -91,7 +187,11 @@ module('Integration | Component | school/vocabulary-new-term', function (hooks) this.set('vocabulary', vocabularyModel); await render( , ); @@ -102,7 +202,7 @@ module('Integration | Component | school/vocabulary-new-term', function (hooks) assert.strictEqual(component.errorMessage, 'Term is a duplicate'); }); - test("can't add nested term with duplicate title", async function (assert) { + test('cannot add nested term with duplicate title', async function (assert) { const title = 'duplicate title'; const vocabulary = await this.server.create('vocabulary'); const term = await this.server.create('term', { @@ -127,6 +227,7 @@ module('Integration | Component | school/vocabulary-new-term', function (hooks) @vocabulary={{this.vocabulary}} @term={{this.term}} @createTerm={{(noop)}} + @cancel={{(noop)}} /> , ); diff --git a/tests/integration/components/school/vocabulary-term-manager-test.gjs b/tests/integration/components/school/vocabulary-term-manager-test.gjs deleted file mode 100644 index 2830af30ab..0000000000 --- a/tests/integration/components/school/vocabulary-term-manager-test.gjs +++ /dev/null @@ -1,449 +0,0 @@ -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'frontend/tests/helpers'; -import { render } from '@ember/test-helpers'; -import { setupMSW } from 'frontend/tests/msw'; -import { component } from 'frontend/tests/pages/components/school/vocabulary-term-manager'; -import VocabularyTermManager from 'frontend/components/school/vocabulary-term-manager'; -import noop from 'frontend/helpers/noop'; - -module('Integration | Component | school/vocabulary-term-manager', function (hooks) { - setupRenderingTest(hooks); - setupMSW(hooks); - - test('it renders', async function (assert) { - const vocabulary = await this.server.create('vocabulary'); - const grandParent = await this.server.create('term', { - title: 'grandparent', - vocabulary, - }); - const parent = await this.server.create('term', { - vocabulary, - parent: grandParent, - }); - const term = await this.server.create('term', { - vocabulary, - parent, - description: 'some description', - }); - const subTerm = await this.server.create('term', { - vocabulary, - parent: term, - active: false, - }); - await this.server.create('term', { - vocabulary, - parent: term, - active: true, - }); - await this.server.create('term', { - vocabulary, - parent: subTerm, - active: false, - }); - - const vocabularyModel = await this.owner - .lookup('service:store') - .findRecord('vocabulary', vocabulary.id); - const termModel = await this.owner.lookup('service:store').findRecord('term', term.id); - - this.set('term', termModel); - this.set('vocabulary', vocabularyModel); - await render( - , - ); - - assert.strictEqual(component.title, `Title: ${term.title}`); - assert.strictEqual(component.description, `Description: ${term.description}`); - assert.strictEqual(component.breadcrumbs.crumbs.length, 5); - assert.strictEqual(component.breadcrumbs.crumbs[0].text, 'All Vocabularies'); - assert.strictEqual(component.breadcrumbs.crumbs[1].text, vocabulary.title); - assert.strictEqual(component.breadcrumbs.crumbs[2].text, grandParent.title); - assert.strictEqual(component.breadcrumbs.crumbs[3].text, parent.title); - assert.strictEqual(component.breadcrumbs.crumbs[4].text, term.title); - assert.strictEqual(component.subTerms.list.length, 2); - assert.strictEqual( - component.subTerms.list[0].text, - 'term 3 This term has sub-terms. (inactive)', - ); - assert.ok(component.subTerms.list[0].hasChildren); - assert.strictEqual(component.subTerms.list[1].text, 'term 4'); - assert.notOk(component.subTerms.list[1].hasChildren); - }); - - test('activate inactive term', async function (assert) { - const vocabulary = await this.server.create('vocabulary'); - const term = await this.server.create('term', { - vocabulary, - active: false, - }); - const vocabularyModel = await this.owner - .lookup('service:store') - .findRecord('vocabulary', vocabulary.id); - const termModel = await this.owner.lookup('service:store').findRecord('term', term.id); - - this.set('term', termModel); - this.set('vocabulary', vocabularyModel); - await render( - , - ); - assert.strictEqual(component.isActive.yesNoToggle.checked, 'false'); - await component.isActive.yesNoToggle.click(); - assert.strictEqual(component.isActive.yesNoToggle.checked, 'true'); - assert.ok(this.server.db.term.all()[0].active); - }); - - test('inactivate active term', async function (assert) { - const vocabulary = await this.server.create('vocabulary'); - const term = await this.server.create('term', { - vocabulary, - active: true, - }); - const vocabularyModel = await this.owner - .lookup('service:store') - .findRecord('vocabulary', vocabulary.id); - const termModel = await this.owner.lookup('service:store').findRecord('term', term.id); - - this.set('term', termModel); - this.set('vocabulary', vocabularyModel); - await render( - , - ); - assert.strictEqual(component.isActive.yesNoToggle.checked, 'true'); - await component.isActive.yesNoToggle.click(); - assert.strictEqual(component.isActive.yesNoToggle.checked, 'false'); - assert.notOk(this.server.db.term.all()[0].active); - }); - - test('change term title', async function (assert) { - const vocabulary = await this.server.create('vocabulary'); - const term = await this.server.create('term', { - vocabulary, - active: true, - }); - const vocabularyModel = await this.owner - .lookup('service:store') - .findRecord('vocabulary', vocabulary.id); - const termModel = await this.owner.lookup('service:store').findRecord('term', term.id); - - this.set('vocabulary', vocabularyModel); - this.set('term', termModel); - await render( - , - ); - assert.strictEqual(component.title, `Title: ${term.title}`); - await component.editTitle(); - await component.changeTitle('new title'); - await component.saveTitle(); - assert.strictEqual(this.server.db.term.all()[0].title, 'new title'); - }); - - test('cancel term title changes', async function (assert) { - const vocabulary = await this.server.create('vocabulary'); - const term = await this.server.create('term', { - vocabulary, - active: true, - }); - const vocabularyModel = await this.owner - .lookup('service:store') - .findRecord('vocabulary', vocabulary.id); - const termModel = await this.owner.lookup('service:store').findRecord('term', term.id); - - this.set('vocabulary', vocabularyModel); - this.set('term', termModel); - await render( - , - ); - assert.strictEqual(component.title, `Title: ${term.title}`); - await component.editTitle(); - await component.changeTitle('new title'); - await component.cancelTitleChanges(); - assert.strictEqual(component.title, `Title: ${term.title}`); - }); - - test('validation fails if term title is blank', async function (assert) { - const vocabulary = await this.server.create('vocabulary'); - const term = await this.server.create('term', { - vocabulary, - active: true, - }); - const vocabularyModel = await this.owner - .lookup('service:store') - .findRecord('vocabulary', vocabulary.id); - const termModel = await this.owner.lookup('service:store').findRecord('term', term.id); - - this.set('vocabulary', vocabularyModel); - this.set('term', termModel); - await render( - , - ); - assert.strictEqual(component.title, `Title: ${term.title}`); - assert.notOk(component.hasError); - await component.editTitle(); - await component.changeTitle(''); - await component.saveTitle(); - assert.ok(component.hasError); - assert.strictEqual(component.error, 'Title can not be blank'); - assert.strictEqual(this.server.db.term.all()[0].title, 'term 0'); - }); - - test('validation fails if term title is too long', async function (assert) { - const vocabulary = await this.server.create('vocabulary'); - const term = await this.server.create('term', { - vocabulary, - active: true, - }); - const vocabularyModel = await this.owner - .lookup('service:store') - .findRecord('vocabulary', vocabulary.id); - const termModel = await this.owner.lookup('service:store').findRecord('term', term.id); - - this.set('vocabulary', vocabularyModel); - this.set('term', termModel); - await render( - , - ); - assert.strictEqual(component.title, `Title: ${term.title}`); - assert.notOk(component.hasError); - await component.editTitle(); - await component.changeTitle('a'.repeat(201)); - await component.saveTitle(); - assert.ok(component.hasError); - assert.strictEqual(component.error, 'Title is too long (maximum is 200 characters)'); - assert.strictEqual(this.server.db.term.all()[0].title, 'term 0'); - }); - - test('add term', async function (assert) { - const vocabulary = await this.server.create('vocabulary'); - const term = await this.server.create('term', { - vocabulary, - active: true, - }); - const store = this.owner.lookup('service:store'); - const vocabularyModel = await store.findRecord('vocabulary', vocabulary.id); - let termModel = await store.findRecord('term', term.id); - let subTerms = termModel.children; - assert.strictEqual(subTerms.length, 0); - this.set('vocabulary', vocabularyModel); - this.set('term', termModel); - await render( - , - ); - assert.strictEqual(component.title, `Title: ${term.title}`); - assert.strictEqual(component.subTerms.list.length, 0); - - await component.subTerms.newTermForm.setTitle('new term'); - await component.subTerms.newTermForm.save(); - termModel = await store.findRecord('term', termModel.id); - subTerms = await termModel.children; - assert.strictEqual(subTerms.length, 1); - assert.strictEqual(component.subTerms.list.length, 1); - assert.strictEqual(subTerms[0].title, 'new term'); - assert.strictEqual((await subTerms[0].vocabulary).id, vocabularyModel.id); - }); - - test("can't add term with empty title", async function (assert) { - const vocabulary = await this.server.create('vocabulary'); - const term = await this.server.create('term', { - vocabulary, - active: true, - }); - const vocabularyModel = await this.owner - .lookup('service:store') - .findRecord('vocabulary', vocabulary.id); - const termModel = await this.owner.lookup('service:store').findRecord('term', term.id); - - this.set('vocabulary', vocabularyModel); - this.set('term', termModel); - await render( - , - ); - assert.strictEqual(component.title, `Title: ${term.title}`); - assert.strictEqual(component.subTerms.list.length, 0); - - assert.notOk(component.subTerms.newTermForm.hasError); - await component.subTerms.newTermForm.setTitle(''); - await component.subTerms.newTermForm.save(); - assert.strictEqual(component.subTerms.list.length, 0); - assert.ok(component.subTerms.newTermForm.hasError); - assert.strictEqual(component.subTerms.newTermForm.errorMessage, 'Term can not be blank'); - }); - - test("can't rename nested term with duplicate title", async function (assert) { - const vocabulary = await this.server.create('vocabulary'); - const topLevelTerm = await this.server.create('term', { - vocabulary, - }); - const term = await this.server.create('term', { - vocabulary, - title: 'term', - active: true, - parent: topLevelTerm, - }); - await this.server.create('term', { - vocabulary, - title: 'duplicate term', - active: true, - parent: topLevelTerm, - }); - - const vocabularyModel = await this.owner - .lookup('service:store') - .findRecord('vocabulary', vocabulary.id); - const termModel = await this.owner.lookup('service:store').findRecord('term', term.id); - - this.set('vocabulary', vocabularyModel); - this.set('term', termModel); - await render( - , - ); - assert.strictEqual(component.title, `Title: ${term.title}`); - assert.notOk(component.hasError); - await component.editTitle(); - await component.changeTitle('duplicate term'); - await component.saveTitle(); - assert.ok(component.hasError); - assert.strictEqual(component.error, 'Title is a duplicate'); - }); - - test("can't rename top-level term with duplicate title", async function (assert) { - const vocabulary = await this.server.create('vocabulary'); - const term = await this.server.create('term', { - vocabulary, - title: 'term', - active: true, - }); - await this.server.create('term', { - vocabulary, - title: 'duplicate term', - active: true, - }); - - const vocabularyModel = await this.owner - .lookup('service:store') - .findRecord('vocabulary', vocabulary.id); - const termModel = await this.owner.lookup('service:store').findRecord('term', term.id); - - this.set('vocabulary', vocabularyModel); - this.set('term', termModel); - await render( - , - ); - assert.strictEqual(component.title, `Title: ${term.title}`); - assert.notOk(component.hasError); - await component.editTitle(); - await component.changeTitle('duplicate term'); - await component.saveTitle(); - assert.ok(component.hasError); - assert.strictEqual(component.error, 'Title is a duplicate'); - }); -}); diff --git a/tests/integration/components/school/vocabulary-terms-list-item-test.gjs b/tests/integration/components/school/vocabulary-terms-list-item-test.gjs new file mode 100644 index 0000000000..e5c91c002a --- /dev/null +++ b/tests/integration/components/school/vocabulary-terms-list-item-test.gjs @@ -0,0 +1,112 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'frontend/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { component } from 'frontend/tests/pages/components/school/vocabulary-terms-list-item'; +import { setupMSW } from 'frontend/tests/msw'; +import VocabularyTermsListItem from 'frontend/components/school/vocabulary-terms-list-item'; +import noop from 'frontend/helpers/noop'; +import noopTask from 'frontend/helpers/noop-task'; + +module('Integration | Component | school/vocabulary-terms-list-item', function (hooks) { + setupRenderingTest(hooks); + setupMSW(hooks); + + hooks.beforeEach(async function () { + const vocabulary = await this.server.create('vocabulary'); + const term = await this.server.create('term', { + vocabulary, + active: true, + }); + + const store = this.owner.lookup('service:store'); + this.termModel = await store.findRecord('term', term.id); + + this.set('term', this.termModel); + }); + + test('it renders', async function (assert) { + await render( + , + ); + + assert.strictEqual(component.title, 'term 0'); + assert.notOk(component.isLabeledAsInactive); + }); + + test('inactive term', async function (assert) { + const vocabulary = await this.server.create('vocabulary'); + const term = await this.server.create('term', { + vocabulary, + active: false, + }); + + const store = this.owner.lookup('service:store'); + const termModel = await store.findRecord('term', term.id); + + await render( + , + ); + + assert.strictEqual(component.title, 'term 1'); + assert.ok(component.isLabeledAsInactive); + }); + + test('read-only list item unclickable', async function (assert) { + const term = await this.server.create('term', { vocabulary: this.vocabulary }); + + const store = this.owner.lookup('service:store'); + const termModel = await store.findRecord('term', term.id); + + this.set('term', [termModel]); + + await render( + , + ); + + assert.notOk(component.hasEditTermForm); + await component.click(); + assert.notOk(component.hasEditTermForm); + }); + + test('clicking list item toggles inline edit-term form if permitted', async function (assert) { + await render( + , + ); + + assert.notOk(component.hasEditTermForm); + await component.click(); + assert.ok(component.hasEditTermForm); + await component.click(); + assert.notOk(component.hasEditTermForm); + }); +}); diff --git a/tests/integration/components/school/vocabulary-terms-list-test.gjs b/tests/integration/components/school/vocabulary-terms-list-test.gjs new file mode 100644 index 0000000000..4ed9b40be9 --- /dev/null +++ b/tests/integration/components/school/vocabulary-terms-list-test.gjs @@ -0,0 +1,126 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'frontend/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { component } from 'frontend/tests/pages/components/school/vocabulary-terms-list'; +import { setupMSW } from 'frontend/tests/msw'; +import VocabularyTermsList from 'frontend/components/school/vocabulary-terms-list'; +import noop from 'frontend/helpers/noop'; +import noopTask from 'frontend/helpers/noop-task'; + +module('Integration | Component | school/vocabulary-terms-list', function (hooks) { + setupRenderingTest(hooks); + setupMSW(hooks); + + test('it renders', async function (assert) { + const vocabulary = await this.server.create('vocabulary'); + const term1 = await this.server.create('term', { vocabulary, title: 'Anatomy', active: true }); + const term2 = await this.server.create('term', { vocabulary, title: 'Biology', active: false }); + await this.server.create('term', { vocabulary, parent: term1, title: 'Brain' }); + await this.server.create('term', { vocabulary, parent: term2, title: 'Biochemistry' }); + + const store = this.owner.lookup('service:store'); + const term1Model = await store.findRecord('term', term1.id); + const term2Model = await store.findRecord('term', term2.id); + + this.set('terms', [term1Model, term2Model]); + + await render( + , + ); + + assert.strictEqual(component.items.length, 2); + assert.strictEqual(component.items[0].title, 'Anatomy'); + assert.notOk(component.items[0].isLabeledAsInactive); + assert.strictEqual(component.nestedLists[0].items[0].title, 'Brain'); + assert.strictEqual(component.items[1].title, 'Biology'); + assert.ok(component.items[1].isLabeledAsInactive); + assert.strictEqual(component.nestedLists[1].items[0].title, 'Biochemistry'); + }); + + test('terms are sorted alphabetically', async function (assert) { + const vocabulary = await this.server.create('vocabulary'); + const term1 = await this.server.create('term', { vocabulary, title: 'Zebra' }); + const term2 = await this.server.create('term', { vocabulary, title: 'Apple' }); + + const store = this.owner.lookup('service:store'); + const t1 = await store.findRecord('term', term1.id); + const t2 = await store.findRecord('term', term2.id); + + this.set('terms', [t1, t2]); + + await render( + , + ); + + assert.strictEqual(component.items[0].title, 'Apple'); + assert.strictEqual(component.items[1].title, 'Zebra'); + }); + + test('clicking list item does nothing if not permitted to update', async function (assert) { + const vocabulary = await this.server.create('vocabulary'); + const term = await this.server.create('term', { vocabulary, title: 'Root' }); + + const store = this.owner.lookup('service:store'); + const termModel = await store.findRecord('term', term.id); + + this.set('terms', [termModel]); + + await render( + , + ); + + assert.notOk(component.items[0].hasEditTermForm); + await component.items[0].click(); + assert.notOk(component.items[0].hasEditTermForm); + }); + + test('clicking list item toggles inline edit-term form if @canUpdate', async function (assert) { + const vocabulary = await this.server.create('vocabulary'); + const term = await this.server.create('term', { vocabulary, title: 'Root' }); + + const store = this.owner.lookup('service:store'); + const termModel = await store.findRecord('term', term.id); + + this.set('terms', [termModel]); + + await render( + , + ); + + assert.notOk(component.items[0].hasEditTermForm); + await component.items[0].click(); + assert.ok(component.items[0].hasEditTermForm); + await component.items[0].click(); + assert.notOk(component.items[0].hasEditTermForm); + }); +}); diff --git a/tests/pages/components/school/vocabularies-expanded.js b/tests/pages/components/school/vocabularies-expanded.js index 4c5f20e7af..75c0632723 100644 --- a/tests/pages/components/school/vocabularies-expanded.js +++ b/tests/pages/components/school/vocabularies-expanded.js @@ -1,5 +1,4 @@ import { clickable, create, text } from 'ember-cli-page-object'; -import termManager from './vocabulary-term-manager'; import vocabularyManager from './vocabulary-manager'; import vocabulariesList from './vocabularies-list'; import newVocabularyForm from './new-vocabulary-form'; @@ -9,7 +8,6 @@ const definition = { title: text('[data-test-vocabularies-title]'), collapse: clickable('[data-test-vocabularies-title]'), openNewVocabularyForm: clickable('[data-test-new-vocabulary] button'), - termManager, vocabularyManager, vocabulariesList, newVocabularyForm, diff --git a/tests/pages/components/school/vocabulary-edit-term.js b/tests/pages/components/school/vocabulary-edit-term.js new file mode 100644 index 0000000000..786668c5a3 --- /dev/null +++ b/tests/pages/components/school/vocabulary-edit-term.js @@ -0,0 +1,26 @@ +import { clickable, create, isPresent, fillable, property, text } from 'ember-cli-page-object'; +import yesNoToggle from 'frontend/tests/pages/components/toggle-yesno'; + +const definition = { + resetScope: true, + scope: '[data-test-school-vocabulary-edit-term]', + title: text('[data-test-vocabulary-edit-term-title]'), + hasAddSubTerm: isPresent('[data-test-add-sub-term]'), + addSubTerm: clickable('[data-test-add-sub-term]'), + hasDeleteTerm: isPresent('[data-test-delete]'), + deleteTerm: clickable('[data-test-delete]'), + deleteTermDisabled: property('disabled', '[data-test-delete]'), + setTitle: fillable('[data-test-vocabulary-term-title] input'), + isActive: { + scope: '[data-test-vocabulary-term-is-active]', + yesNoToggle, + }, + setDescription: fillable('[data-test-vocabulary-term-description] textarea'), + save: clickable('[data-test-submit]'), + cancel: clickable('[data-test-cancel]'), + hasError: isPresent('[data-test-title-validation-error-message]'), + errorMessage: text('[data-test-title-validation-error-message]'), +}; + +export default definition; +export const component = create(definition); diff --git a/tests/pages/components/school/vocabulary-manager.js b/tests/pages/components/school/vocabulary-manager.js index 696e4675d7..40f86d8dfb 100644 --- a/tests/pages/components/school/vocabulary-manager.js +++ b/tests/pages/components/school/vocabulary-manager.js @@ -1,24 +1,31 @@ -import { clickable, create, collection, isPresent, fillable, text } from 'ember-cli-page-object'; +import { clickable, create, isPresent, isVisible, fillable, text } from 'ember-cli-page-object'; import newTermForm from './vocabulary-new-term'; -import breadcrumbs from 'frontend/tests/pages/components/breadcrumbs'; +import termsList from './vocabulary-terms-list'; const definition = { scope: '[data-test-school-vocabulary-manager]', - title: text('[data-test-title]'), - editTitle: clickable('[data-test-title] [data-test-edit]'), - changeTitle: fillable('[data-test-title] input'), - saveTitle: clickable('[data-test-title] .done'), - cancelTitleChanges: clickable('[data-test-title] .cancel'), + backToVocabularies: clickable('[data-test-back-to-vocabularies] a'), + vocabularyTitle: { + scope: '[data-test-vocabulary-title]', + text: text(), + edit: clickable('[data-test-edit]'), + change: fillable('input'), + save: clickable('.done'), + cancelChanges: clickable('.cancel'), + }, hasError: isPresent('[data-test-title-validation-error-message]'), error: text('[data-test-title-validation-error-message]'), - breadcrumbs, + vocabularyTermsTitle: { + scope: '[data-test-vocabulary-terms-title]', + text: text(), + }, + setFilter: fillable('[data-test-filter]'), + toggleNewVocabularyTermForm: clickable('[data-test-expand-collapse-button] button'), + toggleNewVocabularyTermFormExists: isVisible('[data-test-expand-collapse-button]'), + newTermForm, terms: { scope: '[data-test-terms]', - list: collection('[data-test-term-list] [data-test-term]', { - title: text(), - hasChildren: isPresent('[data-test-has-children]'), - }), - newTermForm, + termsList, }, }; diff --git a/tests/pages/components/school/vocabulary-new-sub-term.js b/tests/pages/components/school/vocabulary-new-sub-term.js new file mode 100644 index 0000000000..b2563bf119 --- /dev/null +++ b/tests/pages/components/school/vocabulary-new-sub-term.js @@ -0,0 +1,23 @@ +import { clickable, create, isPresent, fillable, text } from 'ember-cli-page-object'; +import yesNoToggle from 'frontend/tests/pages/components/toggle-yesno'; + +const definition = { + resetScope: true, + scope: '[data-test-school-vocabulary-terms-list-item] + [data-test-school-vocabulary-new-term]', + title: text('[data-test-vocabulary-new-term-title]'), + hasEditTerm: isPresent('[data-test-edit-term]'), + editTerm: clickable('[data-test-edit-term]'), + setTitle: fillable('[data-test-vocabulary-term-title] input'), + isActive: { + scope: '[data-test-vocabulary-term-is-active]', + yesNoToggle, + }, + setDescription: fillable('[data-test-vocabulary-term-description] textarea'), + save: clickable('[data-test-submit]'), + cancel: clickable('[data-test-cancel]'), + hasError: isPresent('[data-test-title-validation-error-message]'), + errorMessage: text('[data-test-title-validation-error-message]'), +}; + +export default definition; +export const component = create(definition); diff --git a/tests/pages/components/school/vocabulary-new-term.js b/tests/pages/components/school/vocabulary-new-term.js index 37a53f224b..84e846dfc7 100644 --- a/tests/pages/components/school/vocabulary-new-term.js +++ b/tests/pages/components/school/vocabulary-new-term.js @@ -1,9 +1,17 @@ import { clickable, create, isPresent, fillable, text } from 'ember-cli-page-object'; +import yesNoToggle from 'frontend/tests/pages/components/toggle-yesno'; const definition = { scope: '[data-test-school-vocabulary-new-term]', - setTitle: fillable('input'), - save: clickable('.save'), + title: text('[data-test-vocabulary-new-term-title]'), + setTitle: fillable('[data-test-vocabulary-term-title] input'), + isActive: { + scope: '[data-test-vocabulary-term-is-active]', + yesNoToggle, + }, + setDescription: fillable('[data-test-vocabulary-term-description] textarea'), + save: clickable('[data-test-submit]'), + cancel: clickable('[data-test-cancel]'), hasError: isPresent('[data-test-title-validation-error-message]'), errorMessage: text('[data-test-title-validation-error-message]'), }; diff --git a/tests/pages/components/school/vocabulary-term-manager.js b/tests/pages/components/school/vocabulary-term-manager.js deleted file mode 100644 index 5733db4398..0000000000 --- a/tests/pages/components/school/vocabulary-term-manager.js +++ /dev/null @@ -1,33 +0,0 @@ -import { clickable, create, collection, isPresent, fillable, text } from 'ember-cli-page-object'; -import newTermForm from './vocabulary-new-term'; -import yesNoToggle from 'frontend/tests/pages/components/toggle-yesno'; -import breadcrumbs from 'frontend/tests/pages/components/breadcrumbs'; - -const definition = { - scope: '[data-test-school-vocabulary-term-manager]', - title: text('[data-test-title] [data-test-label-and-value]'), - editTitle: clickable('[data-test-title] [data-test-edit]'), - changeTitle: fillable('[data-test-title] input'), - saveTitle: clickable('[data-test-title] .done'), - cancelTitleChanges: clickable('[data-test-title] .cancel'), - hasError: isPresent('[data-test-title-validation-error-message]'), - error: text('[data-test-title-validation-error-message]'), - description: text('[data-test-description]'), - delete: clickable('[data-test-delete]'), - breadcrumbs, - isActive: { - scope: '[data-test-is-active]', - yesNoToggle, - }, - subTerms: { - scope: '[data-test-sub-terms]', - list: collection('[data-test-term-list] [data-test-term]', { - title: text(), - hasChildren: isPresent('[data-test-has-children]'), - }), - newTermForm, - }, -}; - -export default definition; -export const component = create(definition); diff --git a/tests/pages/components/school/vocabulary-terms-list-item.js b/tests/pages/components/school/vocabulary-terms-list-item.js new file mode 100644 index 0000000000..9bc1e1b8ef --- /dev/null +++ b/tests/pages/components/school/vocabulary-terms-list-item.js @@ -0,0 +1,35 @@ +import { clickable, create, isPresent, text } from 'ember-cli-page-object'; +import newSubTermForm from './vocabulary-new-sub-term'; +import editTermForm from './vocabulary-edit-term'; + +const definition = { + scope: '[data-test-school-vocabulary-terms-list-item]', + title: text('[data-test-title]'), + isLabeledAsInactive: isPresent('[data-test-inactive]'), + click: clickable(), + hasEditTermForm: isPresent( + '[data-test-school-vocabulary-terms-list-item] + [data-test-school-vocabulary-edit-term]', + { resetScope: true }, + ), + editTermForm, + hasNewTermForm: isPresent( + '[data-test-school-vocabulary-terms-list-item] + [data-test-school-vocabulary-new-term]', + { resetScope: true }, + ), + newSubTermForm, + hasConfirmRemoval: isPresent( + '[data-test-school-vocabulary-terms-list-item] + [data-test-confirm-removal]', + { resetScope: true }, + ), + confirmRemoval: clickable( + '[data-test-school-vocabulary-terms-list-item] + [data-test-confirm-removal] [data-test-confirm-removal-confirm]', + { resetScope: true }, + ), + cancelRemoval: clickable( + '[data-test-school-vocabulary-terms-list-item] + [data-test-confirm-removal] [data-test-confirm-removal-cancel]', + { resetScope: true }, + ), +}; + +export default definition; +export const component = create(definition); diff --git a/tests/pages/components/school/vocabulary-terms-list.js b/tests/pages/components/school/vocabulary-terms-list.js new file mode 100644 index 0000000000..3093c1cee6 --- /dev/null +++ b/tests/pages/components/school/vocabulary-terms-list.js @@ -0,0 +1,16 @@ +import { create, collection } from 'ember-cli-page-object'; +import listItem from './vocabulary-terms-list-item'; + +const definition = { + scope: "[data-test-school-vocabulary-terms-list-level='0']", + items: collection('[data-test-school-vocabulary-terms-list-item-level="0"]', listItem), + nestedLists: collection('[data-test-school-vocabulary-terms-list-level="1"]', { + items: collection('[data-test-school-vocabulary-terms-list-item-level="1"]', listItem), + nestedLists: collection('[data-test-school-vocabulary-terms-list-level="2"]', { + items: collection('[data-test-school-vocabulary-terms-list-item-level="2"]', listItem), + }), + }), +}; + +export default definition; +export const component = create(definition); diff --git a/translations/en-us.yaml b/translations/en-us.yaml index 174681b2e2..e410bad177 100644 --- a/translations/en-us.yaml +++ b/translations/en-us.yaml @@ -18,6 +18,8 @@ general: add: Add addCohort: Add Cohort addNew: Add New + addSubTerm: Add Sub-term + addTerm: Add Term admin: Admin administrator: Administrator administratorCount: "There {count, plural, =1 {is 1 administrator} other {are # administrators}}" @@ -31,7 +33,6 @@ general: allMaterials: All Materials allOtherMembers: "All other members of {topLevelGroupTitle}" allSchools: All Schools - allVocabularies: All Vocabularies allWeeks: All Weeks anything: Anything apiVersionMismatch: API Version Mismatch @@ -111,7 +112,6 @@ general: clearErrors: Clear Errors and Return to Ilios clearFilters: Clear Filters clerkshipType: Clerkship Type - clickToAddTermDescription: Click to add descriptive notes clickToEdit: Click to edit close: Close cohort: Cohort @@ -137,6 +137,7 @@ general: confirmRemoveSession: Are you sure you want to delete this session? This action cannot be undone. confirmRemoveSessionOffering: "Are you sure you want to delete this offering with {learnerGroupCount} learner groups? This action cannot be undone." confirmRemoveSessionType: Are you sure you want to delete this session type? This action cannot be undone. + confirmRemoveTerm: Are you sure you want to delete this term? This action cannot be undone. confirmRemoveVocabulary: Are you sure you want to delete this vocabulary? This action cannot be undone. connectionLost: Connection Lost. contentAuthor: Content Author @@ -241,6 +242,8 @@ general: durationInYears: Duration (in Years) durationMinutes: "{count, plural, =1 {1 minute} other {# minutes}}" edit: Edit + editSubTerm: Edit Sub-term + editTerm: Edit Term email: Email emails: Emails enabled: Enabled @@ -469,6 +472,8 @@ general: newSchool: New School newSession: New Session newSessionType: New Session Type + newSubTerm: New Sub-term + newTerm: New Term newToken: New Token newUpdates: new updates newUser: New User @@ -687,7 +692,6 @@ general: studentAdvisorCount: "There {count, plural, =1 {is 1 student advisor} other {are # student advisors}}" studentAdvisors: Student Advisors subCompetencyCount: "{count, plural, =1 {There is 1 subcompetency} other {There are # subcompetencies}}" - subTerms: Sub-terms subgroupName: Subgroup Name subgroups: Subgroups subjectReports: Subject Reports @@ -714,7 +718,6 @@ general: thereAreXTerms: "{count, plural, =1 {There is 1 term} other {There are # terms}}" thereAreXTypes: "{count, plural, =1 {There is 1 type} other {There are # types}}" thisReportIsNoLongerAvailable: This report is no longer available. - thisTermHasSubTerms: This term has sub-terms. thursday: Thursday time: Time timedRelease: Timed Release @@ -783,8 +786,11 @@ general: vocabularies: Vocabularies vocabulariesBySessionType: Vocabularies by Session Type vocabulary: Vocabulary + vocabularyPlaceholder: Enter a title for this vocabulary + vocabularyTermPlaceholder: Enter a title for this vocabulary term vocabularyTerms: Vocabulary Terms vocabularyTermsFor: "Vocabulary Terms for {subject}" + vocabularyTitle: Vocabulary Title waitSaving: saving... one moment... weak: Weak wednesday: Wednesday diff --git a/translations/es.yaml b/translations/es.yaml index ee79fe057e..4a5e435b72 100644 --- a/translations/es.yaml +++ b/translations/es.yaml @@ -18,6 +18,8 @@ general: add: Agregue addCohort: Agregar Cohorte addNew: Agregue Nuevo + addSubTerm: Agregar Término subordinado + addTerm: Agregar Término admin: Admin administrator: Administrador administratorCount: "Hay {count, plural, =1 {1 administrador} other {# administradores}}" @@ -31,7 +33,6 @@ general: allMaterials: Todos los Materiales allOtherMembers: "Todos otros miembros de {topLevelGroupTitle}" allSchools: Todas las Escuelas - allVocabularies: Todos los Vocabularios allWeeks: Todas las Semanas anything: Caulquier Cosa apiVersionMismatch: Incompatibilidad de versión de API @@ -111,7 +112,6 @@ general: clearErrors: Borrar los Errores y Volver a Ilios clearFilters: Borrar Filtros clerkshipType: Tipo de Rotación - clickToAddTermDescription: Haga clic para agregar notas descriptivas clickToEdit: Hacer clic para editar close: Cerrar cohort: Clase de la Graduación @@ -137,6 +137,7 @@ general: confirmRemoveSession: ¿Está seguro que desea eliminar este sessión? Esta acción no se puede deshacer. confirmRemoveSessionOffering: "¿Estás seguro que quieres borrar este ofrecimiento con {learnerGroupCount} grupos de aprendedores? Esta acción no se puede deshacer." confirmRemoveSessionType: ¿Está seguro de que desea eliminar este tipo de sesión? Esta acción no se puede deshacer. + confirmRemoveTerm: ¿Está seguro de que desea eliminar este término? Esta acción no se puede deshacer. confirmRemoveVocabulary: ¿Está seguro que desea eliminar este vocabulario? Esta accion no se puede deshacer. connectionLost: Conexión Perdida. contentAuthor: Autor del Contenido @@ -241,6 +242,8 @@ general: durationInYears: Duración (en Años) durationMinutes: "{count, plural, =1 {1 minudo} other {# minudos}}" edit: Editar + editSubTerm: Editar Término subordinado + editTerm: Editar Término email: Correo emails: Correos enabled: Activa @@ -469,6 +472,8 @@ general: newSchool: Nueva Escuela newSession: Sesión Nueva newSessionType: Nuevo tipo de sesión + newSubTerm: Nuevo Término Subordinado + newTerm: Nuevo Término newToken: Nuevo Símbolo (Token) newUpdates: Nuevas actualizaciones newUser: Nuevo Usuario @@ -687,7 +692,6 @@ general: studentAdvisorCount: "Hay {count, plural, =1 {1 consejero estudiantil} other {# consejeros estudiantiles}}" studentAdvisors: Consejeros Estudiantiles subCompetencyCount: "{count, plural, =1 {Hay 1 subcompetencia} other {Hay # subcompetencias}}" - subTerms: Términos subordinados subgroupName: Nombre del Subgrupo subgroups: Grupos Subordinados subjectReports: Informes de Sujetos @@ -714,7 +718,6 @@ general: thereAreXTerms: "{count, plural, =1 {Hay 1 término} other {Hay # términos}}" thereAreXTypes: "{count, plural, =1 {Hay 1 tipo} other {Hay # tipos}}" thisReportIsNoLongerAvailable: Este informe ya no está disponible. - thisTermHasSubTerms: Este término tiene subtérminos. thursday: Jueves time: Hora timedRelease: Lanzamiento Calculado @@ -783,8 +786,11 @@ general: vocabularies: Vocabularios vocabulariesBySessionType: Vocabularios por Tipo de Sesión vocabulary: Vocabulario + vocabularyPlaceholder: Meter un titulo para este vocabulario + vocabularyTermPlaceholder: Meter un titulo para este término de vocabulario vocabularyTerms: Términos del Vocabulario vocabularyTermsFor: "Términos del Vocabulario para {subject}" + vocabularyTitle: Titulo de vocabulario waitSaving: Salvando... un momento... weak: Débil wednesday: Miércoles diff --git a/translations/fr.yaml b/translations/fr.yaml index 23845a06e5..eb112103de 100644 --- a/translations/fr.yaml +++ b/translations/fr.yaml @@ -18,6 +18,8 @@ general: add: Ajouté addCohort: Ajouté cohorte addNew: Ajouté Neuf + addSubTerm: Ajouté Sous-Terme + addTerm: Ajouté Terme admin: Admin administrator: Administrateur administratorCount: "Il y a {count, plural, =1 { un administrateur} other {# administrateurs}}" @@ -31,7 +33,6 @@ general: allMaterials: Tous les matériaux allOtherMembers: "Tous les autres membres du {topLevelGroupTitle}" allSchools: Toutes les écoles - allVocabularies: Touts vocabulaires allWeeks: Toutes les Semaines anything: Quelque chose apiVersionMismatch: API conflit de version @@ -111,7 +112,6 @@ general: clearErrors: Supprîme les Erreurs et Retour à Ilios clearFilters: Effacerez les Filtres clerkshipType: Genre de Stage - clickToAddTermDescription: Cliquez pour ajouter des notices descriptives clickToEdit: Clicquez pour modifier close: Fermé cohort: Cohorte @@ -137,6 +137,7 @@ general: confirmRemoveSession: Êtes vous sûr de vouloir supprimer cette séance? Ça action ne peut pas être défait. confirmRemoveSessionOffering: "Êtes-vous sûr vous voulez supprimer cette offre avec {learnerGroupCount} groupes d'apprenants? Ça action ne peut pas être défait." confirmRemoveSessionType: "Voulez-vous vraiment supprimer ce type de session ? Cette action ne peut pas être annulée." + confirmRemoveTerm: "Voulez-vous vraiment supprimer ce terme ? Cette action ne peut pas être annulée." confirmRemoveVocabulary: Êtes vous sûr de vouloir supprimer cette vocabulaire? Cette action ne peut être annulée. connectionLost: Connexion perdu contentAuthor: Auteur @@ -241,6 +242,8 @@ general: durationInYears: Durée (en Années) durationMinutes: "{count, plural, =1 {1 une minute} other {# minutes}}" edit: Edite + editSubTerm: Edite Sous-Terme + editTerm: Edite Terme email: Email emails: Emails enabled: Activé @@ -469,6 +472,8 @@ general: newSchool: École nouvelle newSession: Session neuf newSessionType: Nuevo type de session + newSubTerm: Nouveau sous-terme + newTerm: Nouveau terme newToken: Nouveau jeton newUpdates: Nouvelles mises à jour newUser: Nouvel utilisateur @@ -687,7 +692,6 @@ general: studentAdvisorCount: "Il y a {count, plural, =1 {une camarade-conseil} other {# camarades-conseil}}" studentAdvisors: Camarades-Conseil subCompetencyCount: "{count, plural, =1 {Il y a 1 sous-subcompétence} other {Il y a de # sous-subcompétences}}" - subTerms: Sous-termes subgroupName: nom de sous-groupe subgroups: Sous-groupes subjectReports: Rapports Pédagogiques @@ -714,7 +718,6 @@ general: thereAreXTerms: "{count, plural, =1 {Il y a 1 terme} other {Il y a # termes}}" thereAreXTypes: "{count, plural, =1 {Il y a 1 type} other {Il y a # types}}" thisReportIsNoLongerAvailable: "Ce rapport n'est plus disponsible." - thisTermHasSubTerms: Ce terme a des sous-termes. thursday: Jeudi time: Temps timedRelease: Période Limitée @@ -783,8 +786,11 @@ general: vocabularies: Vocabulaires vocabulariesBySessionType: Vocabulaires par Type de Seance vocabulary: Vocabulaire + vocabularyPlaceholder: Ajoutez un titre pour ce vocabulaire + vocabularyTermPlaceholder: Ajoutez un titre pour ce terme vocabulaire vocabularyTerms: Termes Vocabulaire vocabularyTermsFor: "Termes Vocabulaire par {subject}" + vocabularyTitle: Titre de vocabulaire waitSaving: Travaille dessus... un moment... weak: Faible wednesday: Mercredi