diff --git a/.eslintrc.json b/.eslintrc.json index b24ffefb2..2a33bac4f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,8 @@ "commonjs": false, "es2022": true, "amd": true, - "jest": true + "jest": true, + "cypress": true }, "extends": [ "standard", diff --git a/cypress.config.js b/cypress.config.js index 61bed1e97..c85abfee2 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -5,7 +5,15 @@ module.exports = defineConfig({ baseUrl: 'http://localhost:9001/', screenshotOnRunFailure: false, video: false, - supportFile: false, - specPattern: '**/test/e2e/**/*.cy.{js,jsx}' + supportFile: '**/test/e2e/commands.js', + specPattern: '**/test/e2e/**/*.cy.{js,jsx}', + setupNodeEvents (on, config) { + on('task', { + log(message) { + console.log(message); + return null; + } + }); + } } }); diff --git a/grunt/helpers/Data.js b/grunt/helpers/Data.js index 8c7ea070d..8ee38cada 100644 --- a/grunt/helpers/Data.js +++ b/grunt/helpers/Data.js @@ -70,7 +70,7 @@ class Data { }); language.load(); return language; - }); + }).filter(lang => lang.isValid); this.configFile = new JSONFile({ framework: this.framework, path: path.join(coursePath, `config.${this.jsonext}`) diff --git a/grunt/helpers/data/Language.js b/grunt/helpers/data/Language.js index 003e42db6..2cb10dabe 100644 --- a/grunt/helpers/data/Language.js +++ b/grunt/helpers/data/Language.js @@ -107,18 +107,21 @@ class Language { return index; }, {}); - this.getCourseFileItem(); - return this; } + /** @type {boolean} */ + get isValid() { + return Boolean(this.courseFileItem); + } + /** @type {boolean} */ get hasChanged() { return this.files.some(file => file.hasChanged); } /** - * Produces a manifest file for the Framework data layer at course/language_data_manifest.js. + * Produces a manifest file for the Framework data layer at course/lang/language_data_manifest.js. * @returns {Language} */ saveManifest() { diff --git a/package-lock.json b/package-lock.json index e539ae813..06fea5426 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "adapt_framework", - "version": "5.33.6", + "version": "5.33.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "adapt_framework", - "version": "5.33.6", + "version": "5.33.7", "hasInstallScript": true, "license": "GPL-3.0", "dependencies": { diff --git a/src/core b/src/core index b27a4e964..9a212ca66 160000 --- a/src/core +++ b/src/core @@ -1 +1 @@ -Subproject commit b27a4e9641177f435e8fe95e4c4b5ec3a2e96994 +Subproject commit 9a212ca66e4b9a97738d6fc03a15074f6113eab8 diff --git a/test.js b/test.js index e6a18d7d5..c84d8fa72 100644 --- a/test.js +++ b/test.js @@ -82,10 +82,10 @@ async function waitForGruntServer() { async function cypressRun() { if (argumentValues.testfiles) { - return asyncSpawn('node', './node_modules/cypress/bin/cypress', 'run', '--spec', `${argumentValues.testfiles}`); + return asyncSpawn('node', './node_modules/cypress/bin/cypress', 'run', '--spec', `${argumentValues.testfiles}`, '--config', `{"fixturesFolder": "${argumentValues.outputdir}"}`); } - return asyncSpawn('node', './node_modules/cypress/bin/cypress', 'run'); + return asyncSpawn('node', './node_modules/cypress/bin/cypress', 'run', '--config', `{"fixturesFolder": "${argumentValues.outputdir}"}`); }; async function jestRun() { diff --git a/test/e2e/commands.js b/test/e2e/commands.js new file mode 100644 index 000000000..695657f6c --- /dev/null +++ b/test/e2e/commands.js @@ -0,0 +1,109 @@ +function getBuild() { + try { + return cy.fixture(`adapt/js/build.min.js`).then(build => { + // Return for cy.getBuild().then(build => {}); + // Expose this.build in cypress + return cy.wrap(build).as('build'); + }); + } catch { + cy.task('log', 'fail'); + } +} + +function getConfig() { + return getBuild().then(build => { + // Load the config.json + return cy.fixture(`${build.coursedir}/config.json`).then(config => { + // Return for cy.getConfig().then(config => {}); + // Expose this.config in cypress + return cy.wrap(config).as('config'); + }); + }); +} + +function getData(languageCode = null) { + try { + // Setup data array + const data = []; + // Expose this.data in cypress + cy.wrap(data).as('data'); + // Allow adapt-style shorthand properties: + // this.data.course, this.data.contentObjects, this.data.articles, etc + Object.defineProperties(data, { + course: { + get() { + return data.find(item => item._type === 'course'); + }, + enumerable: false + }, + contentObjects: { + get() { + return data.filter(item => ['menu','page'].includes(item._type)); + }, + enumerable: false + }, + articles: { + get() { + return data.filter(item => item._type === 'article'); + }, + enumerable: false + }, + blocks: { + get() { + return data.filter(item => item._type === 'block'); + }, + enumerable: false + }, + components: { + get() { + return data.filter(item => item._type === 'component'); + }, + enumerable: false + } + }); + return getBuild().then(build => { + const { + coursedir, + availableLanguageNames + } = build; + // Load the config.json + return getConfig().then(config => { + // Check that the specified language is available + const defaultLanguage = config._defaultLanguage; + languageCode = languageCode ?? defaultLanguage; + if (!availableLanguageNames.includes(languageCode)) { + throw new Error(`Language code is not available: ${languageCode}`); + } + // Load the language_data_manifest.js for the default or specified language + cy.fixture(`${coursedir}/${languageCode}/language_data_manifest.js`).then(languageDataManifest => { + // Load each of the files specified in the manifest + languageDataManifest.forEach(localFilePath => { + const filePath = `${coursedir}/${languageCode}/${localFilePath}` + cy.fixture(filePath).then(fileData => { + // Add __index__ and __path__ attributes to each object as in adapt + // so that each object's origin can be identified later if necessary + if (Array.isArray(fileData)) { + fileData.forEach((item, index) => { + item.__index__ = index; + item.__path__ = filePath; + }); + data.push(...fileData); + return; + } + fileData.__path__ = filePath; + data.push(fileData); + }); + }); + }); + // Return for cy.getData(languageCode).then(data => {}); + return cy.wrap(data); + }); + }); + } catch { + cy.task('log', 'fail'); + } +} + +Cypress.Commands.add('getBuild', getBuild); +Cypress.Commands.add('getConfig', getConfig); +Cypress.Commands.add('getData', getData); diff --git a/test/e2e/config.cy.js b/test/e2e/config.cy.js new file mode 100644 index 000000000..9ae160043 --- /dev/null +++ b/test/e2e/config.cy.js @@ -0,0 +1,11 @@ +describe('Config', function () { + + beforeEach(function () { + cy.getConfig(); + }); + + it('should have a valid direction', function () { + expect(this.config._defaultDirection).to.be.oneOf(['ltr', 'rtl']); + }); + +}); diff --git a/test/e2e/languages.cy.js b/test/e2e/languages.cy.js new file mode 100644 index 000000000..8b69c8f62 --- /dev/null +++ b/test/e2e/languages.cy.js @@ -0,0 +1,17 @@ +describe('Languages', function () { + + beforeEach(function () { + cy.getConfig().then(config => cy.getData(config._defaultLanguage)); + }); + + it('should have the default language', function () { + expect(this.build.availableLanguageNames).to.include(this.config._defaultLanguage); + }); + + it('should have data for all specified languages', function () { + this.build.availableLanguageNames.forEach(lang => { + cy.getData(lang); + }); + }); + +}); diff --git a/test/e2e/menuPage.cy.js b/test/e2e/menuPage.cy.js deleted file mode 100644 index 3d8a0239d..000000000 --- a/test/e2e/menuPage.cy.js +++ /dev/null @@ -1,8 +0,0 @@ -describe('Menu Page', () => { - const pageTitle = 'Adapt Version 5'; - - it(`should have the title ${pageTitle}`, () => { - cy.visit('/'); - cy.get('.menu__title-inner').should('contain', pageTitle); - }); -}); diff --git a/test/e2e/trackingIds.cy.js b/test/e2e/trackingIds.cy.js new file mode 100644 index 000000000..f43f82326 --- /dev/null +++ b/test/e2e/trackingIds.cy.js @@ -0,0 +1,22 @@ +describe('Tracking Ids', function () { + + beforeEach(function () { + cy.getBuild(); + }); + + it('should have specified tracking ids for all specified languages', function () { + const { + trackingIdType, + availableLanguageNames + } = this.build; + availableLanguageNames.forEach(lang => { + cy.getData(lang).then(data => { + const trackingIdItems = data.filter(item => item._type === trackingIdType); + trackingIdItems.forEach(item => { + expect(item).to.have.ownProperty('_trackingId'); + }); + }) + }); + }); + +});