diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
commit | 4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch) | |
tree | 5423a1c7516cffe36384133ade12572cf709398d /spec/frontend/lib | |
parent | e570267f2f6b326480d284e0164a6464ba4081bc (diff) |
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'spec/frontend/lib')
-rw-r--r-- | spec/frontend/lib/utils/number_utility_spec.js | 26 | ||||
-rw-r--r-- | spec/frontend/lib/utils/recurrence_spec.js | 333 | ||||
-rw-r--r-- | spec/frontend/lib/utils/text_markdown_spec.js | 19 | ||||
-rw-r--r-- | spec/frontend/lib/utils/uuids_spec.js | 92 | ||||
-rw-r--r-- | spec/frontend/lib/utils/vuex_module_mappers_spec.js | 138 |
5 files changed, 608 insertions, 0 deletions
diff --git a/spec/frontend/lib/utils/number_utility_spec.js b/spec/frontend/lib/utils/number_utility_spec.js index 4dcd9211697..f4483f5098b 100644 --- a/spec/frontend/lib/utils/number_utility_spec.js +++ b/spec/frontend/lib/utils/number_utility_spec.js @@ -10,6 +10,7 @@ import { changeInPercent, formattedChangeInPercent, isNumeric, + isPositiveInteger, } from '~/lib/utils/number_utils'; describe('Number Utils', () => { @@ -184,4 +185,29 @@ describe('Number Utils', () => { expect(isNumeric(value)).toBe(outcome); }); }); + + describe.each` + value | outcome + ${0} | ${true} + ${'0'} | ${true} + ${12345} | ${true} + ${'12345'} | ${true} + ${-1} | ${false} + ${'-1'} | ${false} + ${1.01} | ${false} + ${'1.01'} | ${false} + ${'abcd'} | ${false} + ${'100abcd'} | ${false} + ${'abcd100'} | ${false} + ${''} | ${false} + ${false} | ${false} + ${true} | ${false} + ${undefined} | ${false} + ${null} | ${false} + ${Infinity} | ${false} + `('isPositiveInteger', ({ value, outcome }) => { + it(`when called with ${typeof value} ${value} it returns ${outcome}`, () => { + expect(isPositiveInteger(value)).toBe(outcome); + }); + }); }); diff --git a/spec/frontend/lib/utils/recurrence_spec.js b/spec/frontend/lib/utils/recurrence_spec.js new file mode 100644 index 00000000000..fc22529dffc --- /dev/null +++ b/spec/frontend/lib/utils/recurrence_spec.js @@ -0,0 +1,333 @@ +import { create, free, recall } from '~/lib/utils/recurrence'; + +const HEX = /[a-f0-9]/i; +const HEX_RE = HEX.source; +const UUIDV4 = new RegExp( + `${HEX_RE}{8}-${HEX_RE}{4}-4${HEX_RE}{3}-[89ab]${HEX_RE}{3}-${HEX_RE}{12}`, + 'i', +); + +describe('recurrence', () => { + let recurInstance; + let id; + + beforeEach(() => { + recurInstance = create(); + id = recurInstance.id; + }); + + afterEach(() => { + id = null; + recurInstance.free(); + }); + + describe('create', () => { + it('returns an object with the correct external api', () => { + expect(recurInstance).toMatchObject( + expect.objectContaining({ + id: expect.stringMatching(UUIDV4), + count: 0, + handlers: {}, + free: expect.any(Function), + handle: expect.any(Function), + eject: expect.any(Function), + occur: expect.any(Function), + reset: expect.any(Function), + }), + ); + }); + }); + + describe('recall', () => { + it('returns a previously created RecurInstance', () => { + expect(recall(id).id).toBe(id); + }); + + it("returns undefined if the provided UUID doesn't refer to a stored RecurInstance", () => { + expect(recall('1234')).toBeUndefined(); + }); + }); + + describe('free', () => { + it('returns true when the RecurInstance exists', () => { + expect(free(id)).toBe(true); + }); + + it("returns false when the ID doesn't refer to a known RecurInstance", () => { + expect(free('1234')).toBe(false); + }); + + it('removes the correct RecurInstance from the list of references', () => { + const anotherInstance = create(); + + expect(recall(id)).toEqual(recurInstance); + expect(recall(anotherInstance.id)).toEqual(anotherInstance); + + free(id); + + expect(recall(id)).toBeUndefined(); + expect(recall(anotherInstance.id)).toEqual(anotherInstance); + + anotherInstance.free(); + }); + }); + + describe('RecurInstance (`create()` return value)', () => { + it.each` + property | value | alias + ${'id'} | ${expect.stringMatching(UUIDV4)} | ${'[a string matching the UUIDv4 specification]'} + ${'count'} | ${0} | ${0} + ${'handlers'} | ${{}} | ${{}} + `( + 'has the correct primitive value $alias for the member `$property` to start', + ({ property, value }) => { + expect(recurInstance[property]).toEqual(value); + }, + ); + + describe('id', () => { + it('cannot be changed manually', () => { + expect(() => { + recurInstance.id = 'new-id'; + }).toThrow(TypeError); + + expect(recurInstance.id).toBe(id); + }); + + it.each` + method + ${'free'} + ${'handle'} + ${'eject'} + ${'occur'} + ${'reset'} + `('does not change across any method call - like after `$method`', ({ method }) => { + recurInstance[method](); + + expect(recurInstance.id).toBe(id); + }); + }); + + describe('count', () => { + it('cannot be changed manually', () => { + expect(() => { + recurInstance.count = 9999; + }).toThrow(TypeError); + + expect(recurInstance.count).toBe(0); + }); + + it.each` + method + ${'free'} + ${'handle'} + ${'eject'} + ${'reset'} + `("doesn't change in unexpected scenarios - like after a call to `$method`", ({ method }) => { + recurInstance[method](); + + expect(recurInstance.count).toBe(0); + }); + + it('increments by one each time `.occur()` is called', () => { + expect(recurInstance.count).toBe(0); + recurInstance.occur(); + expect(recurInstance.count).toBe(1); + recurInstance.occur(); + expect(recurInstance.count).toBe(2); + }); + }); + + describe('handlers', () => { + it('cannot be changed manually', () => { + const fn = jest.fn(); + + recurInstance.handle(1, fn); + expect(() => { + recurInstance.handlers = {}; + }).toThrow(TypeError); + + expect(recurInstance.handlers).toStrictEqual({ + 1: fn, + }); + }); + + it.each` + method + ${'free'} + ${'occur'} + ${'eject'} + ${'reset'} + `("doesn't change in unexpected scenarios - like after a call to `$method`", ({ method }) => { + recurInstance[method](); + + expect(recurInstance.handlers).toEqual({}); + }); + + it('adds handlers to the correct slots', () => { + const fn1 = jest.fn(); + const fn2 = jest.fn(); + + recurInstance.handle(100, fn1); + recurInstance.handle(1000, fn2); + + expect(recurInstance.handlers).toMatchObject({ + 100: fn1, + 1000: fn2, + }); + }); + }); + + describe('free', () => { + it('removes itself from recallable memory', () => { + expect(recall(id)).toEqual(recurInstance); + + recurInstance.free(); + + expect(recall(id)).toBeUndefined(); + }); + }); + + describe('handle', () => { + it('adds a handler for the provided count', () => { + const fn = jest.fn(); + + recurInstance.handle(5, fn); + + expect(recurInstance.handlers[5]).toEqual(fn); + }); + + it("doesn't add any handlers if either the count or behavior aren't provided", () => { + const fn = jest.fn(); + + recurInstance.handle(null, fn); + // Note that it's not possible to react to something not happening (without timers) + recurInstance.handle(0, fn); + recurInstance.handle(5, null); + + expect(recurInstance.handlers).toEqual({}); + }); + }); + + describe('eject', () => { + it('removes the handler assigned to the particular count slot', () => { + recurInstance.handle(1, jest.fn()); + + expect(recurInstance.handlers[1]).toBeTruthy(); + + recurInstance.eject(1); + + expect(recurInstance.handlers).toEqual({}); + }); + + it("succeeds (or fails gracefully) when the count provided doesn't have a handler assigned", () => { + recurInstance.eject('abc'); + recurInstance.eject(1); + + expect(recurInstance.handlers).toEqual({}); + }); + + it('makes no changes if no count is provided', () => { + const fn = jest.fn(); + + recurInstance.handle(1, fn); + + recurInstance.eject(); + + expect(recurInstance.handlers[1]).toStrictEqual(fn); + }); + }); + + describe('occur', () => { + it('increments the .count property by 1', () => { + expect(recurInstance.count).toBe(0); + + recurInstance.occur(); + + expect(recurInstance.count).toBe(1); + }); + + it('calls the appropriate handlers', () => { + const fn1 = jest.fn(); + const fn5 = jest.fn(); + const fn10 = jest.fn(); + + recurInstance.handle(1, fn1); + recurInstance.handle(5, fn5); + recurInstance.handle(10, fn10); + + expect(fn1).not.toHaveBeenCalled(); + expect(fn5).not.toHaveBeenCalled(); + expect(fn10).not.toHaveBeenCalled(); + + recurInstance.occur(); + + expect(fn1).toHaveBeenCalledTimes(1); + expect(fn5).not.toHaveBeenCalled(); + expect(fn10).not.toHaveBeenCalled(); + + recurInstance.occur(); + recurInstance.occur(); + recurInstance.occur(); + recurInstance.occur(); + + expect(fn1).toHaveBeenCalledTimes(1); + expect(fn5).toHaveBeenCalledTimes(1); + expect(fn10).not.toHaveBeenCalled(); + + recurInstance.occur(); + recurInstance.occur(); + recurInstance.occur(); + recurInstance.occur(); + recurInstance.occur(); + + expect(fn1).toHaveBeenCalledTimes(1); + expect(fn5).toHaveBeenCalledTimes(1); + expect(fn10).toHaveBeenCalledTimes(1); + }); + }); + + describe('reset', () => { + it('resets the count only, by default', () => { + const fn = jest.fn(); + + recurInstance.handle(3, fn); + recurInstance.occur(); + recurInstance.occur(); + + expect(recurInstance.count).toBe(2); + + recurInstance.reset(); + + expect(recurInstance.count).toBe(0); + expect(recurInstance.handlers).toEqual({ 3: fn }); + }); + + it('also resets the handlers, by specific request', () => { + const fn = jest.fn(); + + recurInstance.handle(3, fn); + recurInstance.occur(); + recurInstance.occur(); + + expect(recurInstance.count).toBe(2); + + recurInstance.reset({ handlersList: true }); + + expect(recurInstance.count).toBe(0); + expect(recurInstance.handlers).toEqual({}); + }); + + it('leaves the count in place, by request', () => { + recurInstance.occur(); + recurInstance.occur(); + + expect(recurInstance.count).toBe(2); + + recurInstance.reset({ currentCount: false }); + + expect(recurInstance.count).toBe(2); + }); + }); + }); +}); diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js index b538257fac0..cad500039c0 100644 --- a/spec/frontend/lib/utils/text_markdown_spec.js +++ b/spec/frontend/lib/utils/text_markdown_spec.js @@ -51,6 +51,25 @@ describe('init markdown', () => { expect(textArea.value).toEqual(`${initialValue}- `); }); + it('inserts dollar signs correctly', () => { + const initialValue = ''; + + textArea.value = initialValue; + textArea.selectionStart = 0; + textArea.selectionEnd = 0; + + insertMarkdownText({ + textArea, + text: textArea.value, + tag: '```suggestion:-0+0\n{text}\n```', + blockTag: true, + selected: '# Does not parse the `$` currently.', + wrap: false, + }); + + expect(textArea.value).toContain('# Does not parse the `$` currently.'); + }); + it('inserts the tag on a new line if the current one is not empty', () => { const initialValue = 'some text'; diff --git a/spec/frontend/lib/utils/uuids_spec.js b/spec/frontend/lib/utils/uuids_spec.js new file mode 100644 index 00000000000..a7770d37566 --- /dev/null +++ b/spec/frontend/lib/utils/uuids_spec.js @@ -0,0 +1,92 @@ +import { uuids } from '~/lib/utils/uuids'; + +const HEX = /[a-f0-9]/i; +const HEX_RE = HEX.source; +const UUIDV4 = new RegExp( + `${HEX_RE}{8}-${HEX_RE}{4}-4${HEX_RE}{3}-[89ab]${HEX_RE}{3}-${HEX_RE}{12}`, + 'i', +); + +describe('UUIDs Util', () => { + describe('uuids', () => { + const SEQUENCE_FOR_GITLAB_SEED = [ + 'a1826a44-316c-480e-a93d-8cdfeb36617c', + 'e049db1f-a4cf-4cba-aa60-6d95e3b547dc', + '6e3c737c-13a7-4380-b17d-601f187d7e69', + 'bee5cc7f-c486-45c0-8ad3-d1ac5402632d', + 'af248c9f-a3a6-4d4f-a311-fe151ffab25a', + ]; + const SEQUENCE_FOR_12345_SEED = [ + 'edfb51e2-e3e1-4de5-90fd-fd1d21760881', + '2f154da4-0a2d-4da9-b45e-0ffed391517e', + '91566d65-8836-4222-9875-9e1df4d0bb01', + 'f6ea6c76-7640-4d71-a736-9d3bec7a1a8e', + 'bfb85869-5fb9-4c5b-a750-5af727ac5576', + ]; + + it('returns version 4 UUIDs', () => { + expect(uuids()[0]).toMatch(UUIDV4); + }); + + it('outputs an array of UUIDs', () => { + const ids = uuids({ count: 11 }); + + expect(ids.length).toEqual(11); + expect(ids.every((id) => UUIDV4.test(id))).toEqual(true); + }); + + it.each` + seeds | uuid + ${['some', 'special', 'seed']} | ${'6fa53e51-0f70-4072-9c84-1c1eee1b9934'} + ${['magic']} | ${'fafae8cd-7083-44f3-b82d-43b30bd27486'} + ${['seeded']} | ${'e06ed291-46c5-4e42-836b-e7c772d48b49'} + ${['GitLab']} | ${'a1826a44-316c-480e-a93d-8cdfeb36617c'} + ${['JavaScript']} | ${'12dfb297-1560-4c38-9775-7178ef8472fb'} + ${[99, 169834, 2619]} | ${'3ecc8ad6-5b7c-4c9b-94a8-c7271c2fa083'} + ${[12]} | ${'2777374b-723b-469b-bd73-e586df964cfd'} + ${[9876, 'mixed!', 7654]} | ${'865212e0-4a16-4934-96f9-103cf36a6931'} + ${[123, 1234, 12345, 6]} | ${'40aa2ee6-0a11-4e67-8f09-72f5eba04244'} + ${[0]} | ${'8c7f0aac-97c4-4a2f-b716-a675d821ccc0'} + `( + 'should always output the UUID $uuid when the options.seeds argument is $seeds', + ({ uuid, seeds }) => { + expect(uuids({ seeds })[0]).toEqual(uuid); + }, + ); + + describe('unseeded UUID randomness', () => { + const nonRandom = Array(6) + .fill(0) + .map((_, i) => uuids({ seeds: [i] })[0]); + const random = uuids({ count: 6 }); + const moreRandom = uuids({ count: 6 }); + + it('is different from a seeded result', () => { + random.forEach((id, i) => { + expect(id).not.toEqual(nonRandom[i]); + }); + }); + + it('is different from other random results', () => { + random.forEach((id, i) => { + expect(id).not.toEqual(moreRandom[i]); + }); + }); + + it('never produces any duplicates', () => { + expect(new Set(random).size).toEqual(random.length); + }); + }); + + it.each` + seed | sequence + ${'GitLab'} | ${SEQUENCE_FOR_GITLAB_SEED} + ${12345} | ${SEQUENCE_FOR_12345_SEED} + `( + 'should output the same sequence of UUIDs for the given seed "$seed"', + ({ seed, sequence }) => { + expect(uuids({ seeds: [seed], count: 5 })).toEqual(sequence); + }, + ); + }); +}); diff --git a/spec/frontend/lib/utils/vuex_module_mappers_spec.js b/spec/frontend/lib/utils/vuex_module_mappers_spec.js new file mode 100644 index 00000000000..d7e51e4daca --- /dev/null +++ b/spec/frontend/lib/utils/vuex_module_mappers_spec.js @@ -0,0 +1,138 @@ +import { mount, createLocalVue } from '@vue/test-utils'; +import Vue from 'vue'; +import Vuex from 'vuex'; +import { + mapVuexModuleActions, + mapVuexModuleGetters, + mapVuexModuleState, + REQUIRE_STRING_ERROR_MESSAGE, +} from '~/lib/utils/vuex_module_mappers'; + +const TEST_MODULE_NAME = 'testModuleName'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +// setup test component and store ---------------------------------------------- +// +// These are used to indirectly test `vuex_module_mappers`. +const TestComponent = Vue.extend({ + props: { + vuexModule: { + type: String, + required: true, + }, + }, + computed: { + ...mapVuexModuleState((vm) => vm.vuexModule, { name: 'name', value: 'count' }), + ...mapVuexModuleGetters((vm) => vm.vuexModule, ['hasValue', 'hasName']), + stateJson() { + return JSON.stringify({ + name: this.name, + value: this.value, + }); + }, + gettersJson() { + return JSON.stringify({ + hasValue: this.hasValue, + hasName: this.hasName, + }); + }, + }, + methods: { + ...mapVuexModuleActions((vm) => vm.vuexModule, ['increment']), + }, + template: ` +<div> + <pre data-testid="state">{{ stateJson }}</pre> + <pre data-testid="getters">{{ gettersJson }}</pre> +</div>`, +}); + +const createTestStore = () => { + return new Vuex.Store({ + modules: { + [TEST_MODULE_NAME]: { + namespaced: true, + state: { + name: 'Lorem', + count: 0, + }, + mutations: { + INCREMENT: (state, amount) => { + state.count += amount; + }, + }, + actions: { + increment({ commit }, amount) { + commit('INCREMENT', amount); + }, + }, + getters: { + hasValue: (state) => state.count > 0, + hasName: (state) => Boolean(state.name.length), + }, + }, + }, + }); +}; + +describe('~/lib/utils/vuex_module_mappers', () => { + let store; + let wrapper; + + const getJsonInTemplate = (testId) => + JSON.parse(wrapper.find(`[data-testid="${testId}"]`).text()); + const getMappedState = () => getJsonInTemplate('state'); + const getMappedGetters = () => getJsonInTemplate('getters'); + + beforeEach(() => { + store = createTestStore(); + + wrapper = mount(TestComponent, { + propsData: { + vuexModule: TEST_MODULE_NAME, + }, + store, + localVue, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('from module defined by prop', () => { + it('maps state', () => { + expect(getMappedState()).toEqual({ + name: store.state[TEST_MODULE_NAME].name, + value: store.state[TEST_MODULE_NAME].count, + }); + }); + + it('maps getters', () => { + expect(getMappedGetters()).toEqual({ + hasName: true, + hasValue: false, + }); + }); + + it('maps action', () => { + jest.spyOn(store, 'dispatch'); + + expect(store.dispatch).not.toHaveBeenCalled(); + + wrapper.vm.increment(10); + + expect(store.dispatch).toHaveBeenCalledWith(`${TEST_MODULE_NAME}/increment`, 10); + }); + }); + + describe('with non-string object value', () => { + it('throws helpful error', () => { + expect(() => mapVuexModuleActions((vm) => vm.bogus, { foo: () => {} })).toThrowError( + REQUIRE_STRING_ERROR_MESSAGE, + ); + }); + }); +}); |