diff --git a/amp.js b/amp.js index 727c5fae82e7..a164240f0c04 100755 --- a/amp.js +++ b/amp.js @@ -58,7 +58,6 @@ createTask('coverage-map', 'coverageMap'); createTask('css'); createTask('default', 'defaultTask', 'default-task'); createTask('dep-check', 'depCheck'); -createTask('dev-dashboard-tests', 'devDashboardTests'); createTask('dist'); createTask('e2e'); createTask('firebase'); diff --git a/build-system/pr-check/build-targets.js b/build-system/pr-check/build-targets.js index ee6e2fa04565..54cb812e0efb 100644 --- a/build-system/pr-check/build-targets.js +++ b/build-system/pr-check/build-targets.js @@ -173,16 +173,6 @@ const targetMatchers = { file == 'build-system/global-configs/caches.json' ); }, - [Targets.DEV_DASHBOARD]: (file) => { - if (isOwnersFile(file)) { - return false; - } - return ( - file == 'build-system/tasks/dev-dashboard-tests.js' || - file == 'build-system/server/app.js' || - file.startsWith('build-system/server/app-index/') - ); - }, [Targets.DOCS]: (file) => { if (isOwnersFile(file)) { return false; diff --git a/build-system/pr-check/checks.js b/build-system/pr-check/checks.js index c5f935168b8b..c8267fb3570c 100644 --- a/build-system/pr-check/checks.js +++ b/build-system/pr-check/checks.js @@ -39,7 +39,6 @@ function pushBuildWorkflow() { timedExecOrDie('amp check-build-system'); timedExecOrDie('amp babel-plugin-tests'); timedExecOrDie('amp caches-json'); - timedExecOrDie('amp dev-dashboard-tests'); timedExecOrDie('amp check-exact-versions'); timedExecOrDie('amp check-renovate-config'); timedExecOrDie('amp server-tests'); @@ -105,10 +104,6 @@ async function prBuildWorkflow() { timedExecOrDie('amp markdown-toc'); } - if (buildTargetsInclude(Targets.DEV_DASHBOARD)) { - timedExecOrDie('amp dev-dashboard-tests'); - } - if (buildTargetsInclude(Targets.OWNERS)) { timedExecOrDie('amp check-owners --local_changes'); // only for PR builds } diff --git a/build-system/server/app-index/test/helpers.js b/build-system/server/app-index/test/helpers.js index 6a5d56b7f4fc..29e2f16ae3c1 100644 --- a/build-system/server/app-index/test/helpers.js +++ b/build-system/server/app-index/test/helpers.js @@ -14,44 +14,45 @@ * limitations under the License. */ -const {expect} = require('chai'); -const {JSDOM} = require('jsdom'); +const posthtml = require('posthtml'); -const parseHtmlChunk = (htmlStr) => { - const {body} = new JSDOM(htmlStr).window.document; - expect(body.children).to.have.length(1); - return body.firstElementChild; -}; +function getElementChildren(content) { + return content?.filter?.((node) => typeof node !== 'string') || []; +} -const boundAttrRe = (attr) => - new RegExp(`\\[${attr}\\]=(("[^"]+")|('[^']+')|([^\\s\\>]+))`); +async function posthtmlGetTextContent(document, matcher) { + let textContent; -// JSDom doesn't parse attributes whose names don't follow the spec, so -// our only way to test [attr] values is via regex. -const getBoundAttr = ({outerHTML}, attr) => { - const match = outerHTML.match(boundAttrRe(attr)); - if (!match) { - return; - } - const [, valuePart] = match; - if (valuePart.charAt(0) == '"' || valuePart.charAt(0) == "'") { - return valuePart.substring(1, valuePart.length - 1); - } - return valuePart; -}; + await posthtml([ + (tree) => { + tree.match(matcher, (node) => { + if (node.content) { + textContent = node.content.join(''); + } + return node; + }); + return tree; + }, + ]).process(document); -const expectValidAmphtml = (validator, string) => { - const {errors: errorsAndWarnings, status} = validator.validateString(string); - const errors = errorsAndWarnings.filter(({severity}) => severity == 'ERROR'); + return textContent; +} - // Compare with empty array instead of checking `to.be.empty` so - // validation errors are output as AssertionErrors. - expect(errors).to.deep.equal([]); - expect(status).to.equal('PASS'); -}; +async function extractMatching(document, matcher) { + const {html} = await posthtml([ + (tree) => { + let matching = ''; + tree.match(matcher, (node) => { + matching = node; + }); + return [matching]; + }, + ]).process(document); + return html.trim() || null; +} module.exports = { - expectValidAmphtml, - getBoundAttr, - parseHtmlChunk, + extractMatching, + getElementChildren, + posthtmlGetTextContent, }; diff --git a/build-system/server/app-index/test/test-amphtml-helpers.js b/build-system/server/app-index/test/test-amphtml-helpers.js index 3e568aa91440..03f4c1a73766 100644 --- a/build-system/server/app-index/test/test-amphtml-helpers.js +++ b/build-system/server/app-index/test/test-amphtml-helpers.js @@ -14,13 +14,8 @@ * limitations under the License. */ -const amphtmlValidator = require('amphtml-validator'); - -const {expectValidAmphtml, parseHtmlChunk} = require('./helpers'); -const {expect} = require('chai'); -const {html} = require('../html'); -const {JSDOM} = require('jsdom'); - +const posthtml = require('posthtml'); +const test = require('ava'); const { AmpDoc, AmpState, @@ -29,395 +24,318 @@ const { containsExpr, ternaryExpr, } = require('../amphtml-helpers'); +const {getElementChildren, posthtmlGetTextContent} = require('./helpers'); +const {html} = require('../html'); -describe('devdash', () => { - describe('AMPHTML helpers', () => { - describe('AmpDoc', () => { - it('fails without args', () => { - expect(() => AmpDoc()).to.throw(); - }); - - it('fails without min required fields', () => { - expect(() => AmpDoc({})).to.throw(); - }); - - it('creates valid doc with min required fields', async () => { - expectValidAmphtml( - await amphtmlValidator.getInstance(), - AmpDoc({ - canonical: '/', - }) - ); - }).timeout(5000); - - it('creates valid doc with set fields', async () => { - expectValidAmphtml( - await amphtmlValidator.getInstance(), - AmpDoc({ - canonical: '/', - css: 'body { font-family:sans-serif; } ', - head: html` `, - body: html`
Hola
`, - }) - ); - }).timeout(5000); - }); - - describe('ampStateKey', () => { - it('concats arguments', () => { - expect(ampStateKey('foo', 'bar')).to.equal('foo.bar'); - expect(ampStateKey('tacos', 'al', 'pastor')).to.equal( - 'tacos.al.pastor' - ); - }); - }); - - describe('ternaryExpr', () => { - it('creates expression', () => { - expect(ternaryExpr('a', 'b', 'c')).to.equal('a ? b : c'); - }); - }); - - describe('containsExpr', () => { - it('creates expression with literals', () => { - expect(containsExpr("'a'", "'b'", "'c'", "'d'")).to.equal( - "'a'.indexOf('b') > -1 ? 'c' : 'd'" - ); - }); - - it('creates expression with vars', () => { - expect(containsExpr('a', 'b', 'c', 'd')).to.equal( - 'a.indexOf(b) > -1 ? c : d' - ); - }); - }); - - describe('AmpState', () => { - it('generates tree', () => { - const id = 'foo'; - const state = 'bar'; - const root = parseHtmlChunk(AmpState(id, state)); +test('AmpDoc fails without args', (t) => { + t.throws(() => AmpDoc()); +}); - expect(root.tagName).to.equal('AMP-STATE'); - expect(root.getAttribute('id')).to.equal(id); +test('AmpDoc fails without min required fields', (t) => { + t.throws(() => AmpDoc({})); +}); - expect(root.children).to.have.length(1); +test('ampStateKey concats arguments', (t) => { + t.is(ampStateKey('foo', 'bar'), 'foo.bar'); + t.is(ampStateKey('tacos', 'al', 'pastor'), 'tacos.al.pastor'); +}); - const {firstElementChild} = root; - expect(firstElementChild.tagName).to.equal('SCRIPT'); - expect(firstElementChild.getAttribute('type')).to.equal( - 'application/json' - ); - }); +test('ternaryExpr creates expression', (t) => { + t.is(ternaryExpr('a', 'b', 'c'), 'a ? b : c'); +}); - it('renders json object', () => { - const id = 'whatever'; - const state = {foo: 'bar', baz: {yes: 'no'}}; +test('containsExpr creates expression with literals', (t) => { + t.is( + containsExpr("'a'", "'b'", "'c'", "'d'"), + "'a'.indexOf('b') > -1 ? 'c' : 'd'" + ); +}); - const {textContent} = parseHtmlChunk( - AmpState(id, state) - ).firstElementChild; +test('containsExpr creates expression with vars', (t) => { + t.is(containsExpr('a', 'b', 'c', 'd'), 'a.indexOf(b) > -1 ? c : d'); +}); - expect(JSON.parse(textContent)).to.deep.equal(state); +function containsExtension(scripts, expectedExtension) { + return scripts.some( + (s) => + s.attrs?.['custom-element'] == expectedExtension && + s.attrs?.['custom-template'] == null + ); +} + +function containsTemplate(scripts, expectedTemplate) { + return scripts.some( + (s) => + s.attrs?.['custom-template'] == expectedTemplate && + s.attrs?.['custom-extension'] == null + ); +} + +async function getScriptNodes(document) { + const scripts = []; + + await posthtml([ + (tree) => { + tree.match({tag: 'script'}, (node) => { + scripts.push(node); + return node; }); + return tree; + }, + ]).process(document); + + return scripts; +} + +test('AmpState generates tree', async (t) => { + const id = 'foo'; + const state = 'bar'; + const document = AmpState(id, state); + + await posthtml([ + (tree) => { + const nodes = getElementChildren(tree); + t.is(nodes.length, 1); + const [root] = nodes; + t.is(root.tag, 'amp-state'); + const rootChildren = getElementChildren(root.content); + t.is(rootChildren.length, 1); + const [firstElementChild] = rootChildren; + t.is(firstElementChild.tag, 'script'); + t.is(firstElementChild.attrs?.type, 'application/json'); + }, + ]).process(document); +}); - it('renders string literal', () => { - const id = 'whatever'; - const state = 'foo'; +test('AmpState renders json object', async (t) => { + const id = 'whatever'; + const state = {foo: 'bar', baz: {yes: 'no'}}; - const {textContent} = parseHtmlChunk( - AmpState(id, state) - ).firstElementChild; + const document = AmpState(id, state); - expect(JSON.parse(textContent)).to.equal(state); - }); + const textContent = await posthtmlGetTextContent(document, { + tag: 'script', + }); - it('renders array', () => { - const id = 'whatever'; - const state = ['foo', 'bar', 'baz']; + t.deepEqual(JSON.parse(textContent), state); +}); - const {textContent} = parseHtmlChunk( - AmpState(id, state) - ).firstElementChild; +test('AmpState renders string literal', async (t) => { + const id = 'whatever'; + const state = 'foo'; - expect(JSON.parse(textContent)).to.deep.equal(state); - }); - }); - - describe('addRequiredExtensionsToHead', () => { - function containsExtension(scripts, expectedExtension) { - return scripts.some( - (s) => - s.getAttribute('custom-element') == expectedExtension && - s.getAttribute('custom-template') == null - ); - } - - function containsTemplate(scripts, expectedTemplate) { - return scripts.some( - (s) => - s.getAttribute('custom-template') == expectedTemplate && - s.getAttribute('custom-extension') == null - ); - } - - it('renders ok', () => { - // eslint-disable-next-line local/html-template - const rawStr = html` - - - - - `; - - expect(new JSDOM(addRequiredExtensionsToHead(rawStr))).to.be.ok; - }); + const document = AmpState(id, state); - it('adds mixed', () => { - const expectedExtensions = [ - 'amp-foo', - 'amp-bar', - 'amp-foo-bar-baz', - 'amp-bind', - 'amp-form', - ]; - - const expectedTemplates = ['amp-mustache']; - - // eslint-disable-next-line local/html-template - const rawStr = html` - - - - - -
- -
- - Text content - -
- - - -
- - `; - - const {document} = new JSDOM(addRequiredExtensionsToHead(rawStr)) - .window; - - const scripts = Array.from( - document.head.getElementsByTagName('script') - ); - - expect(scripts).to.have.length( - expectedExtensions.length + expectedTemplates.length - ); - - scripts.forEach((script) => { - expect(script.getAttribute('src')).to.be.ok; - expect(script.getAttribute('async')).to.equal(''); - }); - - expectedExtensions.forEach((expectedScript) => { - expect(scripts).to.satisfy((scripts) => - containsExtension(scripts, expectedScript) - ); - }); - - expectedTemplates.forEach((expectedScript) => { - expect(scripts).to.satisfy((scripts) => - containsTemplate(scripts, expectedScript) - ); - }); - }); + const textContent = await posthtmlGetTextContent(document, { + tag: 'script', + }); - it('adds extensions', () => { - const expected = ['amp-foo', 'amp-bar', 'amp-foo-bar-baz']; - - // eslint-disable-next-line local/html-template - const rawStr = html` - - - - - -
- -
- - Text content - -
-
- - `; - - const {document} = new JSDOM(addRequiredExtensionsToHead(rawStr)) - .window; - - const scripts = Array.from( - document.head.getElementsByTagName('script') - ); - - expect(scripts).to.have.length(expected.length); - - scripts.forEach((script) => { - expect(script.getAttribute('src')).to.be.ok; - expect(script.getAttribute('async')).to.equal(''); - expect(script.getAttribute('custom-template')).to.be.null; - }); - - expected.forEach((expectedScript) => { - expect(scripts).to.satisfy((scripts) => - containsExtension(scripts, expectedScript) - ); - }); - }); + t.is(JSON.parse(textContent), state); +}); - it('adds template', () => { - const expected = 'amp-mustache'; +test('AmpState renders array', async (t) => { + const id = 'whatever'; + const state = ['foo', 'bar', 'baz']; - // eslint-disable-next-line local/html-template - const rawStr = html` - - - -
- - - -
- - - `; + const document = AmpState(id, state); - const {document} = new JSDOM(addRequiredExtensionsToHead(rawStr)) - .window; + const textContent = await posthtmlGetTextContent(document, { + tag: 'script', + }); - const scripts = document.head.getElementsByTagName('script'); + t.deepEqual(JSON.parse(textContent), state); +}); - expect(scripts).to.have.length(1); +test('addRequiredExtensionsToHead adds mixed', async (t) => { + const expectedExtensions = [ + 'amp-foo', + 'amp-bar', + 'amp-foo-bar-baz', + 'amp-bind', + 'amp-form', + ]; + + const expectedTemplates = ['amp-mustache']; + + // eslint-disable-next-line local/html-template + const rawStr = html` + + + + + + +
+ +
+ + Text content + +
+ + + +
+ + + `; + + const scripts = await getScriptNodes(addRequiredExtensionsToHead(rawStr)); + + t.is(scripts.length, expectedExtensions.length + expectedTemplates.length); + + scripts.forEach((script) => { + t.truthy(script.attrs?.src); + t.is(script.attrs?.async, ''); + }); - const [script] = scripts; + expectedExtensions.forEach((expectedScript) => { + t.assert(containsExtension(scripts, expectedScript)); + }); - expect(script.getAttribute('custom-element')).to.be.null; - expect(script.getAttribute('src')).to.be.ok; - expect(script.getAttribute('async')).to.equal(''); - expect(script.getAttribute('custom-template')).to.equal(expected); - }); + expectedTemplates.forEach((expectedScript) => { + t.assert(containsTemplate(scripts, expectedScript)); + }); +}); - it('adds per
', () => { - const expected = 'amp-form'; +test('addRequiredExtensionsToHead adds extensions', async (t) => { + const expected = ['amp-foo', 'amp-bar', 'amp-foo-bar-baz']; + + // eslint-disable-next-line local/html-template + const rawStr = html` + + + + + + +
+ +
+ + Text content + +
+
+ + + `; + + const scripts = await getScriptNodes(addRequiredExtensionsToHead(rawStr)); + + t.is(scripts.length, expected.length); + + scripts.forEach((script) => { + t.truthy(script.attrs?.src); + t.is(script.attrs?.async, ''); + t.falsy(script.attrs?.['custom-template']); + }); - // eslint-disable-next-line local/html-template - const rawStr = html` - - -
- - `; + expected.forEach((expectedScript) => { + t.assert(containsExtension(scripts, expectedScript)); + }); +}); - const {document} = new JSDOM(addRequiredExtensionsToHead(rawStr)) - .window; +test('addRequiredExtensionsToHead adds template', async (t) => { + const expected = 'amp-mustache'; + + // eslint-disable-next-line local/html-template + const rawStr = html` + + + +
+ + + +
+ + + `; + + const scripts = await getScriptNodes(addRequiredExtensionsToHead(rawStr)); + + t.is(scripts.length, 1); + + const [script] = scripts; + + t.truthy(script.attrs?.src); + t.is(script.attrs?.async, ''); + t.falsy(script.attrs?.['custom-element']); + t.is(script.attrs?.['custom-template'], expected); +}); - const scripts = document.head.getElementsByTagName('script'); +test('addRequiredExtensionsToHead adds per
', async (t) => { + const expected = 'amp-form'; - expect(scripts).to.have.length(1); + // eslint-disable-next-line local/html-template + const rawStr = html` + + +
+ + `; - const [script] = scripts; + const scripts = await getScriptNodes(addRequiredExtensionsToHead(rawStr)); - expect(script.getAttribute('custom-template')).to.be.null; - expect(script.getAttribute('src')).to.be.ok; - expect(script.getAttribute('async')).to.equal(''); - expect(script.getAttribute('custom-element')).to.equal(expected); - }); + t.is(scripts.length, 1); - it('adds per ', () => { - const expected = 'amp-form'; + const [script] = scripts; - // eslint-disable-next-line local/html-template - const rawStr = html` - - - - - - - `; + t.truthy(script.attrs?.src); + t.is(script.attrs?.async, ''); + t.falsy(script.attrs?.['custom-template']); + t.is(script.attrs?.['custom-element'], expected); +}); - const {document} = new JSDOM(addRequiredExtensionsToHead(rawStr)) - .window; +test('addRequiredExtensionsToHead adds per ', async (t) => { + const expected = 'amp-form'; - const scripts = document.head.getElementsByTagName('script'); + // eslint-disable-next-line local/html-template + const rawStr = html` + + + + + + + + + `; - expect(scripts).to.have.length(1); + const scripts = await getScriptNodes(addRequiredExtensionsToHead(rawStr)); - const [script] = scripts; + t.is(scripts.length, 1); - expect(script.getAttribute('custom-template')).to.be.null; - expect(script.getAttribute('src')).to.be.ok; - expect(script.getAttribute('async')).to.equal(''); - expect(script.getAttribute('custom-element')).to.equal(expected); - }); + const [script] = scripts; - it('adds per - - `; +test('addRequiredExtensionsToHead adds per + + + `; - const scripts = document.head.getElementsByTagName('script'); + const scripts = await getScriptNodes(addRequiredExtensionsToHead(rawStr)); - expect(scripts).to.have.length(1); + t.is(scripts.length, 1); - const [script] = scripts; + const [script] = scripts; - expect(script.getAttribute('custom-template')).to.be.null; - expect(script.getAttribute('src')).to.be.ok; - expect(script.getAttribute('async')).to.equal(''); - expect(script.getAttribute('custom-element')).to.equal(expected); - }); - }); - }); + t.truthy(script.attrs?.src); + t.is(script.attrs?.async, ''); + t.falsy(script.attrs?.['custom-template']); + t.is(script.attrs?.['custom-element'], expected); }); diff --git a/build-system/server/app-index/test/test-file-list.js b/build-system/server/app-index/test/test-file-list.js index b5c04fe6f381..01aee9344b8c 100644 --- a/build-system/server/app-index/test/test-file-list.js +++ b/build-system/server/app-index/test/test-file-list.js @@ -14,138 +14,178 @@ * limitations under the License. */ -const {expect} = require('chai'); +const posthtml = require('posthtml'); +const test = require('ava'); +const {extractMatching, getElementChildren} = require('./helpers'); const {FileList} = require('../file-list'); -const {getBoundAttr, parseHtmlChunk} = require('./helpers'); -describe('devdash', () => { - describe('FileList', () => { - it('wraps', () => { - const root = parseHtmlChunk( - FileList({ - basepath: 'basepath', - fileSet: [], - selectModePrefix: '/', - }) - ); - - expect(root.className).to.equal('file-list-container'); +test('wraps', async (t) => { + const document = FileList({ + basepath: 'basepath', + fileSet: [], + selectModePrefix: '/', + }); - const {firstElementChild} = root; + await posthtml([ + (tree) => { + const elements = getElementChildren(tree); + t.is(elements.length, 1); - expect(firstElementChild.className).to.equal('wrap'); - }); + const [root] = elements; + t.is(root.attrs.class, 'file-list-container'); - it('creates amp-list', () => { - const root = parseHtmlChunk( - FileList({ - basepath: 'basepath', - fileSet: [], - selectModePrefix: '/', - }) - ); + const [firstElementChild] = getElementChildren(root.content); + t.is(firstElementChild.attrs.class, 'wrap'); + }, + ]).process(document); +}); - const {length} = root.getElementsByTagName('amp-list'); - expect(length).to.equal(1); - }); +test('creates amp-list', async (t) => { + const document = FileList({ + basepath: 'basepath', + fileSet: [], + selectModePrefix: '/', + }); - it('creates placeholder inside amp-list with rendered data', () => { - const fileSet = ['foo.bar', 'tacos.al.pastor']; + const elements = []; + await posthtml([ + (tree) => + tree.match({tag: 'amp-list'}, (node) => { + elements.push(node); + return node; + }), + ]).process(document); + + const {length} = elements; + t.is(length, 1); +}); - const root = parseHtmlChunk( - FileList({ - fileSet, - basepath: 'basepath', - selectModePrefix: '/', - }) - ); +test('creates placeholder inside amp-list with rendered data', async (t) => { + const fileSet = ['foo.bar', 'tacos.al.pastor']; - const els = root.querySelectorAll('amp-list > [placeholder]'); + const document = FileList({ + fileSet, + basepath: 'basepath', + selectModePrefix: '/', + }); - expect(els).to.have.length(1); + const extractedAmplist = await extractMatching(document, { + tag: 'amp-list', + }); + t.truthy(extractedAmplist); - const [placeholder] = els; - const {firstElementChild} = placeholder; + const extractedPlaceholder = await extractMatching(extractedAmplist, { + attrs: {placeholder: ''}, + }); + t.truthy(extractedPlaceholder); + + let placeholders = []; + const fileLinkContainers = []; + await posthtml([ + (tree) => { + placeholders = getElementChildren(tree); + tree.match({attrs: {class: /file-link-container/}}, (node) => { + const [a] = getElementChildren(node.content); + if (a && a.tag === 'a' && !a.attrs?.['[href]']) { + fileLinkContainers.push(node); + } + }); - expect(firstElementChild.getAttribute('role')).to.equal('list'); + return tree; + }, + ]).process(extractedPlaceholder); - const items = firstElementChild.querySelectorAll('.file-link-container'); + const [placeholder] = placeholders; + const [firstElementChild] = getElementChildren(placeholder.content || []); - expect(items).to.have.length(fileSet.length); - }); + t.is(firstElementChild.attrs?.role, 'list'); + t.is(fileLinkContainers.length, fileSet.length); +}); - it('binds /examples hrefs', () => { - const fileSet = ['asada.html', 'adobada.html', 'pastor.html']; - const basepath = '/examples/'; +async function getListitemAElements(document) { + const extractedList = await extractMatching(document, { + attrs: {role: 'list'}, + }); - const root = parseHtmlChunk( - FileList({ - fileSet, - basepath, - selectModePrefix: '/', - }) - ); + const elements = []; + await posthtml([ + (tree) => + tree.match({attrs: {role: 'listitem'}}, (node) => { + const [a] = getElementChildren(node.content); + if (a && a.tag === 'a' && a.attrs?.href) { + elements.push(a); + } + return node; + }), + ]).process(extractedList); + + return elements; +} + +test('binds /examples hrefs', async (t) => { + const fileSet = ['asada.html', 'adobada.html', 'pastor.html']; + const basepath = '/examples/'; + + const document = FileList({ + fileSet, + basepath, + selectModePrefix: '/', + }); - const els = root.querySelectorAll('amp-list [role=listitem] > a[href]'); + const elements = await getListitemAElements(document); - expect(els).to.have.length(fileSet.length); + t.is(elements.length, fileSet.length); - Array.from(els).forEach((el, i) => { - expect(getBoundAttr(el, 'href')).to.be.ok; - expect(el.getAttribute('href')).to.equal(basepath + fileSet[i]); - }); - }); + for (const [i, element] of elements.entries()) { + t.truthy(element.attrs?.['[href]']); + t.is(element.attrs?.href, basepath + fileSet[i]); + } +}); - it('does not bind non-/examples hrefs', () => { - const fileSet = ['asada.html', 'adobada.html', 'pastor.html']; - const basepath = '/potato/'; +test('does not bind non-/examples hrefs', async (t) => { + const fileSet = ['asada.html', 'adobada.html', 'pastor.html']; + const basepath = '/potato/'; - const root = parseHtmlChunk( - FileList({ - fileSet, - basepath, - selectModePrefix: '/', - }) - ); + const document = FileList({ + fileSet, + basepath, + selectModePrefix: '/', + }); - const els = root.querySelectorAll('amp-list [role=listitem] > a[href]'); + const elements = await getListitemAElements(document); - expect(els).to.have.length(fileSet.length); + t.is(elements.length, fileSet.length); - Array.from(els).forEach((el, i) => { - expect(getBoundAttr(el, 'href')).to.be.undefined; - expect(el.getAttribute('href')).to.equal(basepath + fileSet[i]); - }); - }); + for (const [i, element] of elements.entries()) { + t.falsy(element.attrs?.['[href]']); + t.is(element.attrs?.href, basepath + fileSet[i]); + } +}); - it('binds/does not bind mixed', () => { - const bound = ['asada.html', 'adobada.html', 'pastor.html']; - const notBound = ['chabbuddy.g', 'dj.beats', 'mc.grindah']; - const basepath = '/examples/'; +test('binds/does not bind mixed', async (t) => { + const bound = ['asada.html', 'adobada.html', 'pastor.html']; + const notBound = ['chabbuddy.g', 'dj.beats', 'mc.grindah']; + const basepath = '/examples/'; - const root = parseHtmlChunk( - FileList({ - fileSet: [...bound, ...notBound], - basepath, - selectModePrefix: '/', - }) - ); + const document = FileList({ + fileSet: [...bound, ...notBound], + basepath, + selectModePrefix: '/', + }); - const els = root.querySelectorAll('amp-list [role=listitem] > a[href]'); + const elements = await getListitemAElements(document); - expect(els).to.have.length(bound.length + notBound.length); + t.is(elements.length, bound.length + notBound.length); - bound.forEach((expectedHref, i) => { - const el = els[i]; - expect(getBoundAttr(el, 'href')).to.be.ok; - expect(el.getAttribute('href')).to.equal(basepath + expectedHref); - }); + bound.forEach((expectedHref, i) => { + const element = elements[i]; + t.truthy(element.attrs?.['[href]']); + t.is(element.attrs?.href, basepath + expectedHref); + }); - notBound.forEach((expectedHref, i) => { - const el = els[bound.length + i]; - expect(getBoundAttr(el, 'href')).to.be.undefined; - expect(el.getAttribute('href')).to.equal(basepath + expectedHref); - }); - }); + notBound.forEach((expectedHref, i) => { + const element = elements[bound.length + i]; + t.falsy(element.attrs?.['[href]']); + t.is(element.attrs?.href, basepath + expectedHref); }); }); diff --git a/build-system/server/app-index/test/test-html.js b/build-system/server/app-index/test/test-html.js index fa1a3d671055..180aa2276e37 100644 --- a/build-system/server/app-index/test/test-html.js +++ b/build-system/server/app-index/test/test-html.js @@ -14,30 +14,26 @@ * limitations under the License. */ -const {expect} = require('chai'); +const test = require('ava'); const {html, joinFragments} = require('../html'); -describe('devdash', () => { - describe('html helpers', () => { - describe('joinFragments', () => { - it('joins simple fragments', () => { - expect(joinFragments(['a', 'b', 'c'])).to.equal('abc'); - }); - it('joins mapped fragments', () => { - expect(joinFragments([1, 2, 3], (a) => a + 1)).to.equal('234'); - }); - }); +test('joinFragments joins simple fragments', (t) => { + t.is(joinFragments(['a', 'b', 'c']), 'abc'); +}); + +test('joinFragments joins mapped fragments', (t) => { + t.is( + joinFragments([1, 2, 3], (a) => a + 1), + '234' + ); +}); - describe('html', () => { - it('passes through simple string', () => { - expect(html`foo`).to.equal('foo'); - }); +test('tagged literal passes through simple string', (t) => { + t.is(html`foo`, 'foo'); +}); - it('concatenates interpolated args', () => { - // eslint-disable-next-line local/html-template - const interpolated = html`quesadilla ${'de'} chicharrón ${'con'} queso`; - expect(interpolated).to.equal('quesadilla de chicharrón con queso'); - }); - }); - }); +test('tagged literal concatenates interpolated args', (t) => { + // eslint-disable-next-line local/html-template + const interpolated = html`quesadilla ${'de'} chicharrón ${'con'} queso`; + t.is(interpolated, 'quesadilla de chicharrón con queso'); }); diff --git a/build-system/server/app-index/test/test-self.js b/build-system/server/app-index/test/test-self.js deleted file mode 100644 index 937d5191356b..000000000000 --- a/build-system/server/app-index/test/test-self.js +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Copyright 2019 The AMP HTML Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS-IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const amphtmlValidator = require('amphtml-validator'); -const fs = require('fs'); -const path = require('path'); -const {expectValidAmphtml, getBoundAttr, parseHtmlChunk} = require('./helpers'); -const {expect} = require('chai'); - -describe('devdash', () => { - describe('Test helpers', () => { - describe('parseHtmlChunk', () => { - it('returns firstElementChild', () => { - const {tagName} = parseHtmlChunk(''); - expect(tagName).to.equal(tagName); - }); - - it('fails with multiple children', () => { - expect(() => parseHtmlChunk('')).to.throw(); - }); - - it('fails with text node as content', () => { - expect(() => parseHtmlChunk('text content')).to.throw(); - }); - - it('fails on empty string', () => { - expect(() => parseHtmlChunk('')).to.throw(); - }); - }); - - describe('getBoundAttr', () => { - it('returns bound attr set with other bound attrs', () => { - const fakeEl = {outerHTML: '
'}; - expect(getBoundAttr(fakeEl, 'myHref')).to.equal('a'); - }); - - it('returns bound attr set without quotes with unbound attr', () => { - const fakeEl = {outerHTML: '
'}; - expect(getBoundAttr(fakeEl, 'myHref')).to.equal('a'); - }); - - it('returns bound attr set with quotes with unbound attr', () => { - const fakeEl = {outerHTML: '
'}; - expect(getBoundAttr(fakeEl, 'foo')).to.equal('blah'); - }); - - it('returns bound attr set with quotes', () => { - const fakeEl = {outerHTML: '
'}; - expect(getBoundAttr(fakeEl, 'myHref')).to.equal('b'); - }); - - it('returns bound attr set without quotes', () => { - const fakeEl = {outerHTML: '
'}; - expect(getBoundAttr(fakeEl, 'foo')).to.equal('baz'); - }); - - it('returns undefined when not found', () => { - const fakeEl = {outerHTML: '
'}; - expect(getBoundAttr(fakeEl, 'whatever')).to.be.undefined; - }); - - it('returns value with whitespace', () => { - const valueWithWhitespace = ` ^..^ / - /_/\_____/ - /\ /\ - / \ / \ `; - const fakeEl = { - outerHTML: `
`, - }; - expect(getBoundAttr(fakeEl, 'foo')).to.equal(valueWithWhitespace); - }); - - it('returns value with double quote', () => { - const fakeEl = {outerHTML: "
"}; - expect(getBoundAttr(fakeEl, 'foo')).to.equal('ab"cd ef'); - }); - - it('returns value with single quote', () => { - const fakeEl = {outerHTML: '
'}; - expect(getBoundAttr(fakeEl, 'foo')).to.equal("ab'cd ef"); - }); - }); - - describe('expectValidAmphtml', () => { - it('passes with minimum valid doc', async () => { - const validDocPath = path.join( - __dirname, - '../../../../validator/testdata/feature_tests/minimum_valid_amp.html' - ); - - const validDoc = fs.readFileSync(validDocPath).toString(); - - expectValidAmphtml(await amphtmlValidator.getInstance(), validDoc); - }); - - it('fails with invalid doc', async () => { - const invalidDoc = ''; - - const validator = await amphtmlValidator.getInstance(); - - expect(() => { - expectValidAmphtml(validator, invalidDoc); - }).to.throw(); - }); - - it('ignores errors with severity ≠ ERROR', () => { - const mockValidator = { - validateString: () => ({ - status: 'PASS', - errors: [{severity: 'FOO'}], - }), - }; - expectValidAmphtml(mockValidator, 'any string'); - }); - }); - }); -}); diff --git a/build-system/server/app-index/test/test-template.js b/build-system/server/app-index/test/test-template.js deleted file mode 100644 index 2974931b84d9..000000000000 --- a/build-system/server/app-index/test/test-template.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright 2019 The AMP HTML Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS-IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const amphtmlValidator = require('amphtml-validator'); - -const {expectValidAmphtml} = require('./helpers'); -const {renderTemplate} = require('../template'); - -describe('template', () => { - describe('renderTemplate', () => { - it('renders valid doc', async () => { - expectValidAmphtml( - await amphtmlValidator.getInstance(), - renderTemplate({ - basepath: '/examples/', - css: 'body{}', - isMainPage: true, - fileSet: ['tacos.al.pastor'], - serveMode: 'default', - selectModePrefix: '/', - }) - ); - }); - - it('renders valid doc with empty/default values', async () => { - expectValidAmphtml( - await amphtmlValidator.getInstance(), - renderTemplate() - ); - }); - }); -}); diff --git a/build-system/server/app-index/test/test.js b/build-system/server/app-index/test/test.js index a2b8a3c682a1..d01ed7598291 100644 --- a/build-system/server/app-index/test/test.js +++ b/build-system/server/app-index/test/test.js @@ -14,16 +14,12 @@ * limitations under the License. */ -const {expect} = require('chai'); +const test = require('ava'); const {serveIndexForTesting} = require('../index'); const NOOP = () => {}; -describe('devdash', () => { - describe('express middleware', () => { - it('renders HTML', async () => { - const renderedHtml = await serveIndexForTesting({url: '/'}, {end: NOOP}); - expect(renderedHtml).to.be.ok; - }); - }); +test('renders HTML', async (t) => { + const renderedHtml = await serveIndexForTesting({url: '/'}, {end: NOOP}); + t.truthy(renderedHtml); }); diff --git a/build-system/tasks/ava.js b/build-system/tasks/ava.js index e0554cc98b01..6930952780a4 100644 --- a/build-system/tasks/ava.js +++ b/build-system/tasks/ava.js @@ -25,6 +25,7 @@ async function ava() { // These need equivalents for CI in build-system/pr-check/build-targets.js // (see targetMatchers[Targets.AVA]) const testFiles = [ + 'build-system/server/app-index/test/*test*.js', 'build-system/server/test/app-utils.test.js', 'build-system/tasks/get-zindex/get-zindex.test.js', 'build-system/tasks/make-extension/test/test.js', diff --git a/build-system/tasks/dev-dashboard-tests.js b/build-system/tasks/dev-dashboard-tests.js deleted file mode 100644 index e3dc9d10c89d..000000000000 --- a/build-system/tasks/dev-dashboard-tests.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright 2019 The AMP HTML Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS-IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -'use strict'; - -const config = require('../test-configs/config'); -const globby = require('globby'); -const Mocha = require('mocha'); -const {isCiBuild} = require('../common/ci'); - -/** - * Run all the dev dashboard tests - * @return {!Promise} - */ -async function devDashboardTests() { - const mocha = new Mocha({ - reporter: isCiBuild() ? 'mocha-silent-reporter' : 'spec', - }); - - // Add our files - const allDevDashboardTests = globby.sync(config.devDashboardTestPaths); - allDevDashboardTests.forEach((file) => { - mocha.addFile(file); - }); - - // Create our deffered - let resolver; - const deferred = new Promise((resolverIn) => { - resolver = resolverIn; - }); - - // Run the tests. - mocha.run(function (failures) { - if (failures) { - process.exitCode = 1; - } - resolver(); - }); - return deferred; -} - -module.exports = { - devDashboardTests, -}; - -devDashboardTests.description = 'Run all the dev dashboard tests'; diff --git a/build-system/tasks/pr-check.js b/build-system/tasks/pr-check.js index 7616187c7335..44b9aa55bb4c 100644 --- a/build-system/tasks/pr-check.js +++ b/build-system/tasks/pr-check.js @@ -93,10 +93,6 @@ async function prCheck() { runCheck('amp check-links --local_changes'); } - if (buildTargetsInclude(Targets.DEV_DASHBOARD)) { - runCheck('amp dev-dashboard-tests'); - } - if (buildTargetsInclude(Targets.OWNERS)) { runCheck('amp check-owners'); }