Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 18:44:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 18:44:42 +0300
commit4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch)
tree5423a1c7516cffe36384133ade12572cf709398d /spec/frontend/lib
parente570267f2f6b326480d284e0164a6464ba4081bc (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.js26
-rw-r--r--spec/frontend/lib/utils/recurrence_spec.js333
-rw-r--r--spec/frontend/lib/utils/text_markdown_spec.js19
-rw-r--r--spec/frontend/lib/utils/uuids_spec.js92
-rw-r--r--spec/frontend/lib/utils/vuex_module_mappers_spec.js138
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,
+ );
+ });
+ });
+});