From 440169a7cbb30e8e69980475b3f6bda47aaf5389 Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Sat, 5 Jun 2021 20:15:13 +0200 Subject: [PATCH] feat: conditionally include files (#42205) Co-authored-by: Shaun Hamilton <51722130+ShaunSHamilton@users.noreply.github.com> --- .../Challenges/rechallenge/builders.js | 29 ++++++++++-- .../Challenges/rechallenge/builders.test.js | 46 +++++++++++++++++++ .../Challenges/rechallenge/transformers.js | 32 ++++++++++++- utils/polyvinyl.js | 11 +++++ 4 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 client/src/templates/Challenges/rechallenge/builders.test.js diff --git a/client/src/templates/Challenges/rechallenge/builders.js b/client/src/templates/Challenges/rechallenge/builders.js index 5d1a0dbf6e..faba74e344 100644 --- a/client/src/templates/Challenges/rechallenge/builders.js +++ b/client/src/templates/Challenges/rechallenge/builders.js @@ -56,6 +56,18 @@ export const cssToHtml = cond([ [stubTrue, identity] ]); +export function findIndexHtml(files) { + const filtered = files.filter(file => wasHtmlFile(file)); + if (filtered.length > 1) { + throw new Error('Too many html blocks in the challenge seed'); + } + return filtered[0]; +} + +function wasHtmlFile(file) { + return file.history[0] === 'index.html'; +} + export function concatHtml({ required = [], template, files = [] } = {}) { const createBody = template ? _template(template) : defaultTemplate; const head = required @@ -75,10 +87,19 @@ A required file can not have both a src and a link: src = ${src}, link = ${link} }) .reduce((head, element) => head.concat(element)); - const source = files.reduce( - (source, file) => source.concat(file.contents, htmlCatch), - '' - ); + const indexHtml = findIndexHtml(files); + + const source = files.reduce((source, file) => { + if (!indexHtml) return source.concat(file.contents, htmlCatch); + if ( + indexHtml.importedFiles.includes(file.history[0]) || + wasHtmlFile(file) + ) { + return source.concat(file.contents, htmlCatch); + } else { + return source; + } + }, ''); return `${head}${createBody({ source })}`; } diff --git a/client/src/templates/Challenges/rechallenge/builders.test.js b/client/src/templates/Challenges/rechallenge/builders.test.js new file mode 100644 index 0000000000..a62db2e29f --- /dev/null +++ b/client/src/templates/Challenges/rechallenge/builders.test.js @@ -0,0 +1,46 @@ +/* global expect */ +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 b31e7acadc..a3926f9b8a 100644 --- a/client/src/templates/Challenges/rechallenge/transformers.js +++ b/client/src/templates/Challenges/rechallenge/transformers.js @@ -16,6 +16,7 @@ import { transformContents, transformHeadTailAndContents, setExt, + setImportedFiles, compileHeadTail } from '../../../../../utils/polyvinyl'; import createWorker from '../utils/worker-executor'; @@ -213,6 +214,35 @@ const transformHtml = async function (file) { return transformContents(() => div.innerHTML, file); }; +// 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 transformIncludes = async function (fileP) { + const file = await fileP; + const div = document.createElement('div'); + div.innerHTML = file.contents; + const link = + div.querySelector('link[href="styles.css"]') ?? + div.querySelector('link[href="./styles.css"]'); + const script = + div.querySelector('script[src="script.js"]') ?? + div.querySelector('script[src="./script.js"]'); + const importedFiles = []; + if (link) { + importedFiles.push('index.css'); + link.remove(); + } + if (script) { + importedFiles.push('index.js'); + script.remove(); + } + + return flow( + partial(setImportedFiles, importedFiles), + partial(transformContents, () => div.innerHTML) + )(file); +}; + export const composeHTML = cond([ [ testHTML, @@ -229,7 +259,7 @@ export const composeHTML = cond([ ]); export const htmlTransformer = cond([ - [testHTML, transformHtml], + [testHTML, flow(transformHtml, transformIncludes)], [stubTrue, identity] ]); diff --git a/utils/polyvinyl.js b/utils/polyvinyl.js index bfc536afc5..53f973056d 100644 --- a/utils/polyvinyl.js +++ b/utils/polyvinyl.js @@ -87,6 +87,16 @@ function setExt(ext, poly) { return newPoly; } +// setImportedFiles(importedFiles: String[], poly: PolyVinyl) => PolyVinyl +function setImportedFiles(importedFiles, poly) { + checkPoly(poly); + const newPoly = { + ...poly, + importedFiles: [...importedFiles] + }; + return newPoly; +} + // clearHeadTail(poly: PolyVinyl) => PolyVinyl function clearHeadTail(poly) { checkPoly(poly); @@ -139,6 +149,7 @@ module.exports = { isPoly, setContent, setExt, + setImportedFiles, compileHeadTail, transformContents, transformHeadTailAndContents