diff options
Diffstat (limited to 'spec/frontend/__helpers__/matchers')
6 files changed, 215 insertions, 0 deletions
diff --git a/spec/frontend/__helpers__/matchers/index.js b/spec/frontend/__helpers__/matchers/index.js new file mode 100644 index 00000000000..76571bafb06 --- /dev/null +++ b/spec/frontend/__helpers__/matchers/index.js @@ -0,0 +1,3 @@ +export * from './to_have_sprite_icon'; +export * from './to_have_tracking_attributes'; +export * from './to_match_interpolated_text'; diff --git a/spec/frontend/__helpers__/matchers/to_have_sprite_icon.js b/spec/frontend/__helpers__/matchers/to_have_sprite_icon.js new file mode 100644 index 00000000000..bce9d93bea8 --- /dev/null +++ b/spec/frontend/__helpers__/matchers/to_have_sprite_icon.js @@ -0,0 +1,36 @@ +export const toHaveSpriteIcon = (element, iconName) => { + if (!iconName) { + throw new Error('toHaveSpriteIcon is missing iconName argument!'); + } + + if (!(element instanceof HTMLElement)) { + throw new Error(`${element} is not a DOM element!`); + } + + const iconReferences = [].slice.apply(element.querySelectorAll('svg use')); + const matchingIcon = iconReferences.find( + (reference) => reference.parentNode.getAttribute('data-testid') === `${iconName}-icon`, + ); + + const pass = Boolean(matchingIcon); + + let message; + if (pass) { + message = `${element.outerHTML} contains the sprite icon "${iconName}"!`; + } else { + message = `${element.outerHTML} does not contain the sprite icon "${iconName}"!`; + + const existingIcons = iconReferences.map((reference) => { + const iconUrl = reference.getAttribute('href'); + return `"${iconUrl.replace(/^.+#/, '')}"`; + }); + if (existingIcons.length > 0) { + message += ` (only found ${existingIcons.join(',')})`; + } + } + + return { + pass, + message: () => message, + }; +}; diff --git a/spec/frontend/__helpers__/matchers/to_have_tracking_attributes.js b/spec/frontend/__helpers__/matchers/to_have_tracking_attributes.js new file mode 100644 index 00000000000..fd3f3aa042f --- /dev/null +++ b/spec/frontend/__helpers__/matchers/to_have_tracking_attributes.js @@ -0,0 +1,35 @@ +import { diff } from 'jest-diff'; +import { isObject, mapValues, isEqual } from 'lodash'; + +export const toHaveTrackingAttributes = (actual, obj) => { + if (!(actual instanceof Element)) { + return { actual, message: () => 'The received value must be an Element.', pass: false }; + } + + if (!isObject(obj)) { + return { + message: () => `The matching object must be an object. Found ${obj}.`, + pass: false, + }; + } + + const actualAttributes = mapValues(obj, (val, key) => actual.getAttribute(`data-track-${key}`)); + + const matcherPass = isEqual(actualAttributes, obj); + + const failMessage = () => { + // We can match, but still fail because we're in a `expect...not.` context + if (matcherPass) { + return `Expected the element's tracking attributes not to match. Found that they matched ${JSON.stringify( + obj, + )}.`; + } + + const objDiff = diff(actualAttributes, obj); + return `Expected the element's tracking attributes to match the given object. Diff: +${objDiff} +`; + }; + + return { actual, message: failMessage, pass: matcherPass }; +}; diff --git a/spec/frontend/__helpers__/matchers/to_have_tracking_attributes_spec.js b/spec/frontend/__helpers__/matchers/to_have_tracking_attributes_spec.js new file mode 100644 index 00000000000..74073ed4063 --- /dev/null +++ b/spec/frontend/__helpers__/matchers/to_have_tracking_attributes_spec.js @@ -0,0 +1,65 @@ +import { diff } from 'jest-diff'; + +describe('custom matcher toHaveTrackingAttributes', () => { + const createElementWithAttrs = (attributes) => { + const el = document.createElement('div'); + + Object.entries(attributes).forEach(([key, value]) => { + el.setAttribute(key, value); + }); + + return el; + }; + + it('blows up if actual is not an element', () => { + expect(() => { + expect({}).toHaveTrackingAttributes({}); + }).toThrow('The received value must be an Element.'); + }); + + it('blows up if expected is not an object', () => { + expect(() => { + expect(createElementWithAttrs({})).toHaveTrackingAttributes('foo'); + }).toThrow('The matching object must be an object.'); + }); + + it('prints diff when fails', () => { + const expectedDiff = diff({ label: 'foo' }, { label: 'a' }); + expect(() => { + expect(createElementWithAttrs({ 'data-track-label': 'foo' })).toHaveTrackingAttributes({ + label: 'a', + }); + }).toThrow( + `Expected the element's tracking attributes to match the given object. Diff:\n${expectedDiff}\n`, + ); + }); + + describe('positive assertions', () => { + it.each` + attrs | expected + ${{ 'data-track-label': 'foo' }} | ${{ label: 'foo' }} + ${{ 'data-track-label': 'foo' }} | ${{}} + ${{ 'data-track-label': 'foo', label: 'bar' }} | ${{ label: 'foo' }} + ${{ 'data-track-label': 'foo', 'data-track-extra': '123' }} | ${{ label: 'foo', extra: '123' }} + ${{ 'data-track-label': 'foo', 'data-track-extra': '123' }} | ${{ extra: '123' }} + ${{ label: 'foo', extra: '123', id: '7' }} | ${{}} + `('$expected matches element with attrs $attrs', ({ attrs, expected }) => { + expect(createElementWithAttrs(attrs)).toHaveTrackingAttributes(expected); + }); + }); + + describe('negative assertions', () => { + it.each` + attrs | expected + ${{}} | ${{ label: 'foo' }} + ${{ label: 'foo' }} | ${{ label: 'foo' }} + ${{ 'data-track-label': 'bar', label: 'foo' }} | ${{ label: 'foo' }} + ${{ 'data-track-label': 'foo' }} | ${{ extra: '123' }} + ${{ 'data-track-label': 'foo', 'data-track-extra': '123' }} | ${{ label: 'foo', extra: '456' }} + ${{ 'data-track-label': 'foo', 'data-track-extra': '123' }} | ${{ label: 'foo', extra: '123', action: 'click' }} + ${{ label: 'foo', extra: '123', id: '7' }} | ${{ id: '7' }} + `('$expected does not match element with attrs $attrs', ({ attrs, expected }) => { + expect(createElementWithAttrs(attrs)).not.toHaveTrackingAttributes(expected); + }); + }); +}); diff --git a/spec/frontend/__helpers__/matchers/to_match_interpolated_text.js b/spec/frontend/__helpers__/matchers/to_match_interpolated_text.js new file mode 100644 index 00000000000..4ce814a01b4 --- /dev/null +++ b/spec/frontend/__helpers__/matchers/to_match_interpolated_text.js @@ -0,0 +1,30 @@ +export const toMatchInterpolatedText = (received, match) => { + let clearReceived; + let clearMatch; + + try { + clearReceived = received.replace(/\s\s+/gm, ' ').replace(/\s\./gm, '.').trim(); + } catch (e) { + return { actual: received, message: 'The received value is not a string', pass: false }; + } + try { + clearMatch = match.replace(/%{\w+}/gm, '').trim(); + } catch (e) { + return { message: 'The comparator value is not a string', pass: false }; + } + const pass = clearReceived === clearMatch; + const message = pass + ? () => ` + \n\n + Expected: ${this.utils.printExpected(clearReceived)} + To not equal: ${this.utils.printReceived(clearMatch)} + ` + : () => + ` + \n\n + Expected: ${this.utils.printExpected(clearReceived)} + To equal: ${this.utils.printReceived(clearMatch)} + `; + + return { actual: received, message, pass }; +}; diff --git a/spec/frontend/__helpers__/matchers/to_match_interpolated_text_spec.js b/spec/frontend/__helpers__/matchers/to_match_interpolated_text_spec.js new file mode 100644 index 00000000000..f6fd00011fe --- /dev/null +++ b/spec/frontend/__helpers__/matchers/to_match_interpolated_text_spec.js @@ -0,0 +1,46 @@ +describe('custom matcher toMatchInterpolatedText', () => { + describe('malformed input', () => { + it.each([null, 1, Symbol, Array, Object])( + 'fails graciously if the expected value is %s', + (expected) => { + expect(expected).not.toMatchInterpolatedText('null'); + }, + ); + }); + describe('malformed matcher', () => { + it.each([null, 1, Symbol, Array, Object])( + 'fails graciously if the matcher is %s', + (matcher) => { + expect('null').not.toMatchInterpolatedText(matcher); + }, + ); + }); + + describe('positive assertion', () => { + it.each` + htmlString | templateString + ${'foo'} | ${'foo'} + ${'foo'} | ${'foo%{foo}'} + ${'foo '} | ${'foo'} + ${'foo '} | ${'foo%{foo}'} + ${'foo . '} | ${'foo%{foo}.'} + ${'foo bar . '} | ${'foo%{foo} bar.'} + ${'foo\n\nbar . '} | ${'foo%{foo} bar.'} + ${'foo bar . .'} | ${'foo%{fooStart} bar.%{fooEnd}.'} + `('$htmlString equals $templateString', ({ htmlString, templateString }) => { + expect(htmlString).toMatchInterpolatedText(templateString); + }); + }); + + describe('negative assertion', () => { + it.each` + htmlString | templateString + ${'foo'} | ${'bar'} + ${'foo'} | ${'bar%{foo}'} + ${'foo'} | ${'@{lol}foo%{foo}'} + ${' fo o '} | ${'foo'} + `('$htmlString does not equal $templateString', ({ htmlString, templateString }) => { + expect(htmlString).not.toMatchInterpolatedText(templateString); + }); + }); +}); |