diff --git a/client/gatsby-node.js b/client/gatsby-node.js index 3bc54eacca0..736fa2ff580 100644 --- a/client/gatsby-node.js +++ b/client/gatsby-node.js @@ -96,6 +96,7 @@ exports.createPages = function createPages({ graphql, actions, reporter }) { head tail history + fileKey } solutions { contents diff --git a/client/src/templates/Challenges/rechallenge/builders.js b/client/src/templates/Challenges/rechallenge/builders.js index 8bcc5f270c5..67eee543427 100644 --- a/client/src/templates/Challenges/rechallenge/builders.js +++ b/client/src/templates/Challenges/rechallenge/builders.js @@ -1,65 +1,6 @@ -import { - cond, - flow, - identity, - matchesProperty, - partial, - stubTrue, - template as _template -} from 'lodash-es'; +import { template as _template } from 'lodash-es'; -import { - compileHeadTail, - setExt, - transformContents -} from '../../../../../utils/polyvinyl'; - -const wrapInScript = partial( - transformContents, - content => `` -); -const wrapInStyle = partial( - transformContents, - content => `` -); -const setExtToHTML = partial(setExt, 'html'); -const concatHeadTail = partial(compileHeadTail, ''); - -export const jsToHtml = cond([ - [ - matchesProperty('ext', 'js'), - flow(concatHeadTail, wrapInScript, setExtToHTML) - ], - [stubTrue, identity] -]); - -export const cssToHtml = cond([ - [ - matchesProperty('ext', 'css'), - flow(concatHeadTail, wrapInStyle, setExtToHTML) - ], - [stubTrue, identity] -]); - -export function findIndexHtml(challengeFiles) { - const filtered = challengeFiles.filter(challengeFile => - wasHtmlFile(challengeFile) - ); - if (filtered.length > 1) { - throw new Error('Too many html blocks in the challenge seed'); - } - return filtered[0]; -} - -function wasHtmlFile(challengeFile) { - return challengeFile.history[0] === 'index.html'; -} - -export function concatHtml({ - required = [], - template, - challengeFiles = [] -} = {}) { +export function concatHtml({ required = [], template, contents } = {}) { const embedSource = template ? _template(template) : ({ source }) => source; const head = required .map(({ link, src }) => { @@ -78,19 +19,5 @@ A required file can not have both a src and a link: src = ${src}, link = ${link} }) .join('\n'); - const indexHtml = findIndexHtml(challengeFiles); - - const source = challengeFiles.reduce((source, challengeFile) => { - if (!indexHtml) return source.concat(challengeFile.contents); - if ( - indexHtml.importedFiles.includes(challengeFile.history[0]) || - wasHtmlFile(challengeFile) - ) { - return source.concat(challengeFile.contents); - } else { - return source; - } - }, ''); - - return `${head}${embedSource({ source })}`; + return `${head}${embedSource({ source: contents })}`; } diff --git a/client/src/templates/Challenges/rechallenge/builders.test.js b/client/src/templates/Challenges/rechallenge/builders.test.js deleted file mode 100644 index 2dd9e3a947e..00000000000 --- a/client/src/templates/Challenges/rechallenge/builders.test.js +++ /dev/null @@ -1,45 +0,0 @@ -import { findIndexHtml } from './builders.js'; - -const withHTML = [ - { history: ['index.html'], contents: 'the index html' }, - { history: ['index.css', 'index.html'], contents: 'the style file' } -]; - -const withoutHTML = [ - { history: ['index.css', 'index.html'], contents: 'the js file' }, - { history: ['index.js', 'index.html'], contents: 'the style file' } -]; - -const tooMuchHTML = [ - { history: ['index.html'], contents: 'the index html' }, - { history: ['index.css', 'index.html'], contents: 'index html two' }, - { history: ['index.html'], contents: 'index html three' } -]; - -// TODO: write tests for concatHtml instead, since findIndexHtml should not be -// exported. - -describe('findIndexHtml', () => { - it('should return the index.html file from an array', () => { - expect.assertions(1); - - expect(findIndexHtml(withHTML)).toStrictEqual({ - history: ['index.html'], - contents: 'the index html' - }); - }); - - it('should return undefined when the index.html file is missing', () => { - expect.assertions(1); - - expect(findIndexHtml(withoutHTML)).toBeUndefined(); - }); - - it('should throw if there are two or more index.htmls', () => { - expect.assertions(1); - - expect(() => findIndexHtml(tooMuchHTML)).toThrowError( - 'Too many html blocks in the challenge seed' - ); - }); -}); diff --git a/client/src/templates/Challenges/rechallenge/transformers.js b/client/src/templates/Challenges/rechallenge/transformers.js index 264084f5cd6..08e402ffbb5 100644 --- a/client/src/templates/Challenges/rechallenge/transformers.js +++ b/client/src/templates/Challenges/rechallenge/transformers.js @@ -16,7 +16,6 @@ import { transformContents, transformHeadTailAndContents, setExt, - setImportedFiles, compileHeadTail } from '../../../../../utils/polyvinyl'; import createWorker from '../utils/worker-executor'; @@ -204,44 +203,60 @@ async function transformScript(documentElement) { }); } -// Find if the base html refers to the css or js files and record if they do. If -// the link or script exists we remove those elements since those files don't -// exist on the site, only in the editor -const addImportedFiles = async function (fileP) { - const file = await fileP; - const transform = documentElement => { +// This does the final transformations of the files needed to embed them into +// HTML. +export const embedFilesInHtml = async function (challengeFiles) { + const { indexHtml, stylesCss, scriptJs, indexJsx } = + challengeFilesToObject(challengeFiles); + + const embedStylesAndScript = (documentElement, contentDocument) => { const link = documentElement.querySelector('link[href="styles.css"]') ?? documentElement.querySelector('link[href="./styles.css"]'); const script = documentElement.querySelector('script[src="script.js"]') ?? documentElement.querySelector('script[src="./script.js"]'); - const importedFiles = []; if (link) { - importedFiles.push('styles.css'); - link.remove(); + const style = contentDocument.createElement('style'); + style.innerHTML = stylesCss?.contents; + + link.parentNode.replaceChild(style, link); } if (script) { - importedFiles.push('script.js'); - script.remove(); + const script = (contentDocument.createElement('script').innerHTML = + scriptJs?.contents); + link.parentNode.replaceChild(script, link); } return { - contents: documentElement.innerHTML, - importedFiles + contents: documentElement.innerHTML }; }; - const { importedFiles, contents } = await transformWithFrame( - transform, - file.contents - ); - - return flow( - partial(setImportedFiles, importedFiles), - partial(transformContents, () => contents) - )(file); + if (indexHtml) { + const { contents } = await transformWithFrame( + embedStylesAndScript, + indexHtml.contents + ); + return [challengeFiles, contents]; + } else if (indexJsx) { + return [challengeFiles, ``]; + } else if (scriptJs) { + return [challengeFiles, ``]; + } else { + throw Error('No html or js(x) file found'); + } }; +function challengeFilesToObject(challengeFiles) { + const indexHtml = challengeFiles.find(file => file.fileKey === 'indexhtml'); + const indexJsx = challengeFiles.find( + file => file.fileKey === 'indexjs' && file.history[0] === 'index.jsx' + ); + const stylesCss = challengeFiles.find(file => file.fileKey === 'stylescss'); + const scriptJs = challengeFiles.find(file => file.fileKey === 'scriptjs'); + return { indexHtml, indexJsx, stylesCss, scriptJs }; +} + const transformWithFrame = async function (transform, contents) { // we use iframe here since file.contents is destined to be be inserted into // the root of an iframe. @@ -260,7 +275,10 @@ const transformWithFrame = async function (transform, contents) { // itself. It appears that the frame's documentElement can get replaced by a // blank documentElement without the contents. This seems only to happen on // Firefox. - out = await transform(frame.contentDocument.documentElement); + out = await transform( + frame.contentDocument.documentElement, + frame.contentDocument + ); } finally { document.body.removeChild(frame); } @@ -280,19 +298,14 @@ const transformHtml = async function (file) { return transformContents(() => contents, file); }; -const composeHTML = cond([ - [testHTML, partial(compileHeadTail, '')], - [stubTrue, identity] -]); - const htmlTransformer = cond([ - [testHTML, flow(transformHtml, addImportedFiles)], + [testHTML, flow(transformHtml)], [stubTrue, identity] ]); export const getTransformers = options => [ replaceNBSP, babelTransformer(options ? options : {}), - composeHTML, + partial(compileHeadTail, ''), htmlTransformer ]; diff --git a/client/src/templates/Challenges/utils/build.js b/client/src/templates/Challenges/utils/build.js index c85e434b7f8..23a596fbf21 100644 --- a/client/src/templates/Challenges/utils/build.js +++ b/client/src/templates/Challenges/utils/build.js @@ -1,8 +1,8 @@ import frameRunnerData from '../../../../../config/client/frame-runner.json'; import testEvaluatorData from '../../../../../config/client/test-evaluator.json'; import { challengeTypes } from '../../../../utils/challenge-types'; -import { cssToHtml, jsToHtml, concatHtml } from '../rechallenge/builders.js'; -import { getTransformers } from '../rechallenge/transformers'; +import { concatHtml } from '../rechallenge/builders.js'; +import { getTransformers, embedFilesInHtml } from '../rechallenge/transformers'; import { createTestFramer, runTestInTestFrame, @@ -141,19 +141,19 @@ export function buildDOMChallenge( const loadEnzyme = challengeFiles.some( challengeFile => challengeFile.ext === 'jsx' ); - const toHtml = [jsToHtml, cssToHtml]; - const pipeLine = composeFunctions(...getTransformers(), ...toHtml); + const pipeLine = composeFunctions(...getTransformers()); const finalFiles = challengeFiles.map(pipeLine); return Promise.all(finalFiles) .then(checkFilesErrors) - .then(challengeFiles => { + .then(embedFilesInHtml) + .then(([challengeFiles, contents]) => { return { challengeType: challengeTypes.html || challengeTypes.multifileCertProject, build: concatHtml({ required: finalRequires, template, - challengeFiles + contents }), sources: buildSourceMap(challengeFiles), loadEnzyme diff --git a/curriculum/challenges/english/14-responsive-web-design-22/learn-basic-css-by-building-a-cafe-menu/5f3477cb2e27333b1ab2b955.md b/curriculum/challenges/english/14-responsive-web-design-22/learn-basic-css-by-building-a-cafe-menu/5f3477cb2e27333b1ab2b955.md index 329f529998b..f2af63ca109 100644 --- a/curriculum/challenges/english/14-responsive-web-design-22/learn-basic-css-by-building-a-cafe-menu/5f3477cb2e27333b1ab2b955.md +++ b/curriculum/challenges/english/14-responsive-web-design-22/learn-basic-css-by-building-a-cafe-menu/5f3477cb2e27333b1ab2b955.md @@ -15,7 +15,7 @@ Your code should have a `link` element. ```js // link is removed -> if exists, replaced with style -const link = document.querySelector('body > style'); +const link = document.querySelector('head > style'); assert(link); ```