diff options
Diffstat (limited to 'spec/frontend/notes')
11 files changed, 216 insertions, 226 deletions
diff --git a/spec/frontend/notes/components/__snapshots__/notes_app_spec.js.snap b/spec/frontend/notes/components/__snapshots__/notes_app_spec.js.snap index 5f4b3e04a79..bc29903d4bf 100644 --- a/spec/frontend/notes/components/__snapshots__/notes_app_spec.js.snap +++ b/spec/frontend/notes/components/__snapshots__/notes_app_spec.js.snap @@ -3,15 +3,15 @@ exports[`note_app when sort direction is asc shows skeleton notes after the loaded discussions 1`] = ` "<ul id=\\"notes-list\\" class=\\"notes main-notes-list timeline\\"> <noteable-discussion-stub discussion=\\"[object Object]\\" renderdifffile=\\"true\\" helppagepath=\\"\\" isoverviewtab=\\"true\\"></noteable-discussion-stub> - <skeleton-loading-container-stub></skeleton-loading-container-stub> - <discussion-filter-note-stub style=\\"display: none;\\"></discussion-filter-note-stub> + <skeleton-loading-container-stub class=\\"note-skeleton\\"></skeleton-loading-container-stub> + <!----> </ul>" `; exports[`note_app when sort direction is desc shows skeleton notes before the loaded discussions 1`] = ` "<ul id=\\"notes-list\\" class=\\"notes main-notes-list timeline\\"> - <skeleton-loading-container-stub></skeleton-loading-container-stub> + <skeleton-loading-container-stub class=\\"note-skeleton\\"></skeleton-loading-container-stub> <noteable-discussion-stub discussion=\\"[object Object]\\" renderdifffile=\\"true\\" helppagepath=\\"\\" isoverviewtab=\\"true\\"></noteable-discussion-stub> - <discussion-filter-note-stub style=\\"display: none;\\"></discussion-filter-note-stub> + <!----> </ul>" `; diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js index 55e4ef42e37..701ff492702 100644 --- a/spec/frontend/notes/components/comment_form_spec.js +++ b/spec/frontend/notes/components/comment_form_spec.js @@ -7,7 +7,7 @@ import Vuex from 'vuex'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import batchComments from '~/batch_comments/stores/modules/batch_comments'; import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests'; -import createFlash from '~/flash'; +import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import CommentForm from '~/notes/components/comment_form.vue'; import CommentTypeDropdown from '~/notes/components/comment_type_dropdown.vue'; @@ -71,11 +71,19 @@ describe('issue_comment_form component', () => { }; const notableDataMockCanUpdateIssuable = createNotableDataMock({ - current_user: { can_update: true, can_create_note: true }, + current_user: { can_update: true, can_create_note: true, can_create_confidential_note: true }, }); const notableDataMockCannotUpdateIssuable = createNotableDataMock({ - current_user: { can_update: false, can_create_note: true }, + current_user: { + can_update: false, + can_create_note: false, + can_create_confidential_note: false, + }, + }); + + const notableDataMockCannotCreateConfidentialNote = createNotableDataMock({ + current_user: { can_update: false, can_create_note: true, can_create_confidential_note: false }, }); const mountComponent = ({ @@ -490,7 +498,7 @@ describe('issue_comment_form component', () => { await nextTick(); await nextTick(); - expect(createFlash).toHaveBeenCalledWith({ + expect(createAlert).toHaveBeenCalledWith({ message: `Something went wrong while closing the ${type}. Please try again later.`, }); }); @@ -526,7 +534,7 @@ describe('issue_comment_form component', () => { await nextTick(); await nextTick(); - expect(createFlash).toHaveBeenCalledWith({ + expect(createAlert).toHaveBeenCalledWith({ message: `Something went wrong while reopening the ${type}. Please try again later.`, }); }); @@ -562,6 +570,17 @@ describe('issue_comment_form component', () => { expect(checkbox.element.checked).toBe(false); }); + it('should not render checkbox if user is not at least a reporter', () => { + mountComponent({ + mountFunction: mount, + initialData: { note: 'confidential note' }, + noteableData: { ...notableDataMockCannotCreateConfidentialNote }, + }); + + const checkbox = findConfidentialNoteCheckbox(); + expect(checkbox.exists()).toBe(false); + }); + it.each` noteableType | rendered | message ${'Issue'} | ${true} | ${'render'} diff --git a/spec/frontend/notes/components/diff_discussion_header_spec.js b/spec/frontend/notes/components/diff_discussion_header_spec.js index 5800f68b114..bb44563b87a 100644 --- a/spec/frontend/notes/components/diff_discussion_header_spec.js +++ b/spec/frontend/notes/components/diff_discussion_header_spec.js @@ -42,7 +42,7 @@ describe('diff_discussion_header component', () => { expect(props).toMatchObject({ src: firstNoteAuthor.avatar_url, alt: firstNoteAuthor.name, - size: { default: 24, md: 32 }, + size: 32, }); }); }); diff --git a/spec/frontend/notes/components/discussion_actions_spec.js b/spec/frontend/notes/components/discussion_actions_spec.js index d16c13d6fd3..e414ada1854 100644 --- a/spec/frontend/notes/components/discussion_actions_spec.js +++ b/spec/frontend/notes/components/discussion_actions_spec.js @@ -81,7 +81,7 @@ describe('DiscussionActions', () => { }); }); - it(shouldRender ? 'renders resolve buttons' : 'does not render resolve buttons', () => { + it(`${shouldRender ? 'renders' : 'does not render'} resolve buttons`, () => { expect(wrapper.findComponent(ResolveDiscussionButton).exists()).toBe(shouldRender); expect(wrapper.findComponent(ResolveWithIssueButton).exists()).toBe(shouldRender); }); diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js index 76177229cff..b870cda2a24 100644 --- a/spec/frontend/notes/components/note_header_spec.js +++ b/spec/frontend/notes/components/note_header_spec.js @@ -1,10 +1,7 @@ -import { GlSprintf } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import NoteHeader from '~/notes/components/note_header.vue'; -import { AVAILABILITY_STATUS } from '~/set_status_modal/constants'; -import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue'; Vue.use(Vuex); @@ -23,7 +20,6 @@ describe('NoteHeader component', () => { const findTimestamp = () => wrapper.findComponent({ ref: 'noteTimestamp' }); const findInternalNoteIndicator = () => wrapper.findByTestId('internalNoteIndicator'); const findSpinner = () => wrapper.findComponent({ ref: 'spinner' }); - const findAuthorStatus = () => wrapper.findComponent({ ref: 'authorStatus' }); const statusHtml = '"<span class="user-status-emoji has-tooltip" title="foo bar" data-html="true" data-placement="top"><gl-emoji title="basketball and hoop" data-name="basketball" data-unicode-version="6.0">🏀</gl-emoji></span>"'; @@ -37,22 +33,14 @@ describe('NoteHeader component', () => { username: 'root', show_status: true, status_tooltip_html: statusHtml, - availability: '', }; - const createComponent = (props, userAttributes = false) => { + const createComponent = (props) => { wrapper = shallowMountExtended(NoteHeader, { store: new Vuex.Store({ actions, }), propsData: { ...props }, - stubs: { GlSprintf, UserNameWithStatus }, - provide: { - glFeatures: { - removeUserAttributesProjects: userAttributes, - removeUserAttributesGroups: userAttributes, - }, - }, }); }; @@ -61,26 +49,6 @@ describe('NoteHeader component', () => { wrapper = null; }); - describe('when removeUserAttributesProjects feature flag is enabled', () => { - it('does not render busy status', () => { - createComponent({ author: { ...author, availability: AVAILABILITY_STATUS.BUSY } }, true); - - expect(wrapper.find('.note-header-info').text()).not.toContain('(Busy)'); - }); - - it('does not render author status', () => { - createComponent({ author }, true); - - expect(findAuthorStatus().exists()).toBe(false); - }); - - it('does not render username', () => { - createComponent({ author }, true); - - expect(wrapper.find('.note-header-info').text()).not.toContain('@'); - }); - }); - it('does not render discussion actions when includeToggle is false', () => { createComponent({ includeToggle: false, @@ -145,39 +113,6 @@ describe('NoteHeader component', () => { expect(wrapper.find('.js-user-link').exists()).toBe(true); }); - - it('renders busy status if author availability is set', () => { - createComponent({ author: { ...author, availability: AVAILABILITY_STATUS.BUSY } }); - - expect(wrapper.find('.js-user-link').text()).toContain('(Busy)'); - }); - - it('renders author status', () => { - createComponent({ author }); - - expect(findAuthorStatus().exists()).toBe(true); - }); - - it('does not render author status if show_status=false', () => { - createComponent({ - author: { ...author, status: { availability: AVAILABILITY_STATUS.BUSY }, show_status: false }, - }); - - expect(findAuthorStatus().exists()).toBe(false); - }); - - it('does not render author status if status_tooltip_html=null', () => { - createComponent({ - author: { - ...author, - status: { availability: AVAILABILITY_STATUS.BUSY }, - status_tooltip_html: null, - }, - }); - - expect(findAuthorStatus().exists()).toBe(false); - }); - it('renders deleted user text if author is not passed as a prop', () => { createComponent(); @@ -270,24 +205,6 @@ describe('NoteHeader component', () => { }); }); - describe('when author status tooltip is opened', () => { - it('removes `title` attribute from emoji to prevent duplicate tooltips', () => { - createComponent({ - author: { - ...author, - status_tooltip_html: statusHtml, - }, - }); - - return nextTick().then(() => { - const authorStatus = findAuthorStatus(); - authorStatus.trigger('mouseenter'); - - expect(authorStatus.find('gl-emoji').attributes('title')).toBeUndefined(); - }); - }); - }); - describe('when author username link is hovered', () => { it('toggles hover specific CSS classes on author name link', async () => { createComponent({ author }); @@ -327,4 +244,18 @@ describe('NoteHeader component', () => { ); }); }); + + it('does render username', () => { + createComponent({ author }, true); + + expect(wrapper.find('.note-header-info').text()).toContain('@'); + }); + + describe('with system note', () => { + it('does not render username', () => { + createComponent({ author, isSystemNote: true }, true); + + expect(wrapper.find('.note-header-info').text()).not.toContain('@'); + }); + }); }); diff --git a/spec/frontend/notes/components/noteable_note_spec.js b/spec/frontend/notes/components/noteable_note_spec.js index b044d40cbe4..3d7195752d3 100644 --- a/spec/frontend/notes/components/noteable_note_spec.js +++ b/spec/frontend/notes/components/noteable_note_spec.js @@ -214,7 +214,7 @@ describe('issue_note', () => { expect(avatarProps.src).toBe(author.avatar_url); expect(avatarProps.entityName).toBe(author.username); expect(avatarProps.alt).toBe(author.name); - expect(avatarProps.size).toEqual({ default: 24, md: 32 }); + expect(avatarProps.size).toEqual(32); }); it('should render note header content', () => { diff --git a/spec/frontend/notes/components/notes_activity_header_spec.js b/spec/frontend/notes/components/notes_activity_header_spec.js new file mode 100644 index 00000000000..5b3165bf401 --- /dev/null +++ b/spec/frontend/notes/components/notes_activity_header_spec.js @@ -0,0 +1,67 @@ +import { shallowMount } from '@vue/test-utils'; +import { __ } from '~/locale'; +import NotesActivityHeader from '~/notes/components/notes_activity_header.vue'; +import DiscussionFilter from '~/notes/components/discussion_filter.vue'; +import TimelineToggle from '~/notes/components/timeline_toggle.vue'; +import createStore from '~/notes/stores'; +import waitForPromises from 'helpers/wait_for_promises'; +import { notesFilters } from '../mock_data'; + +describe('~/notes/components/notes_activity_header.vue', () => { + let wrapper; + + const findTitle = () => wrapper.find('h2'); + + const createComponent = ({ props = {}, ...options } = {}) => { + wrapper = shallowMount(NotesActivityHeader, { + propsData: { + notesFilters, + ...props, + }, + // why: Rendering async timeline toggle requires store + store: createStore(), + ...options, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('default', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders title', () => { + expect(findTitle().text()).toBe(__('Activity')); + }); + + it('renders discussion filter', () => { + expect(wrapper.findComponent(DiscussionFilter).props()).toEqual({ + filters: notesFilters, + selectedValue: 0, + }); + }); + + it('does not render timeline toggle', () => { + expect(wrapper.findComponent(TimelineToggle).exists()).toBe(false); + }); + }); + + it('with notesFilterValue prop, passes to discussion filter', () => { + createComponent({ props: { notesFilterValue: 1 } }); + + expect(wrapper.findComponent(DiscussionFilter).props('selectedValue')).toBe(1); + }); + + it('with showTimelineViewToggle injected, renders timeline toggle asynchronously', async () => { + createComponent({ provide: { showTimelineViewToggle: () => true } }); + + expect(wrapper.findComponent(TimelineToggle).exists()).toBe(false); + + await waitForPromises(); + + expect(wrapper.findComponent(TimelineToggle).exists()).toBe(true); + }); +}); diff --git a/spec/frontend/notes/components/notes_app_spec.js b/spec/frontend/notes/components/notes_app_spec.js index d4cb07d97dc..9051fcab97f 100644 --- a/spec/frontend/notes/components/notes_app_spec.js +++ b/spec/frontend/notes/components/notes_app_spec.js @@ -11,6 +11,7 @@ import axios from '~/lib/utils/axios_utils'; import * as urlUtility from '~/lib/utils/url_utility'; import CommentForm from '~/notes/components/comment_form.vue'; import NotesApp from '~/notes/components/notes_app.vue'; +import NotesActivityHeader from '~/notes/components/notes_activity_header.vue'; import * as constants from '~/notes/constants'; import createStore from '~/notes/stores'; import '~/behaviors/markdown/render_gfm'; @@ -20,11 +21,14 @@ import * as mockData from '../mock_data'; const TYPE_COMMENT_FORM = 'comment-form'; const TYPE_NOTES_LIST = 'notes-list'; +const TEST_NOTES_FILTER_VALUE = 1; const propsData = { noteableData: mockData.noteableDataMock, notesData: mockData.notesDataMock, userData: mockData.userDataMock, + notesFilters: mockData.notesFilters, + notesFilterValue: TEST_NOTES_FILTER_VALUE, }; describe('note_app', () => { @@ -47,7 +51,7 @@ describe('note_app', () => { axiosMock = new AxiosMockAdapter(axios); store = createStore(); - mountComponent = () => { + mountComponent = ({ props = {} } = {}) => { return mount( { components: { @@ -58,7 +62,10 @@ describe('note_app', () => { </div>`, }, { - propsData, + propsData: { + ...propsData, + ...props, + }, store, }, ); @@ -144,6 +151,13 @@ describe('note_app', () => { it('updates discussions badge', () => { expect(document.querySelector('.js-discussions-count').textContent).toEqual('2'); }); + + it('should render notes activity header', () => { + expect(wrapper.findComponent(NotesActivityHeader).props()).toEqual({ + notesFilterValue: TEST_NOTES_FILTER_VALUE, + notesFilters: mockData.notesFilters, + }); + }); }); describe('render with comments disabled', () => { @@ -151,8 +165,15 @@ describe('note_app', () => { setHTMLFixture('<div class="js-discussions-count"></div>'); axiosMock.onAny().reply(mockData.getIndividualNoteResponse); - store.state.commentsDisabled = true; - wrapper = mountComponent(); + wrapper = mountComponent({ + // why: In this integration test, previously we manually set store.state.commentsDisabled + // This stopped working when we added `<discussion-filter>` into the component tree. + // Let's lean into the integration scope and use a prop that "disables comments". + props: { + notesFilterValue: constants.HISTORY_ONLY_FILTER_VALUE, + }, + }); + return waitForPromises(); }); @@ -358,7 +379,7 @@ describe('note_app', () => { it('should listen hashchange event', () => { const notesApp = wrapper.findComponent(NotesApp); const hash = 'some dummy hash'; - jest.spyOn(urlUtility, 'getLocationHash').mockReturnValueOnce(hash); + jest.spyOn(urlUtility, 'getLocationHash').mockReturnValue(hash); const setTargetNoteHash = jest.spyOn(notesApp.vm, 'setTargetNoteHash'); window.dispatchEvent(new Event('hashchange'), hash); diff --git a/spec/frontend/notes/mixins/discussion_navigation_spec.js b/spec/frontend/notes/mixins/discussion_navigation_spec.js index 1b4e8026d84..45625d0a23f 100644 --- a/spec/frontend/notes/mixins/discussion_navigation_spec.js +++ b/spec/frontend/notes/mixins/discussion_navigation_spec.js @@ -4,7 +4,6 @@ import Vuex from 'vuex'; import { setHTMLFixture } from 'helpers/fixtures'; import createEventHub from '~/helpers/event_hub_factory'; import * as utils from '~/lib/utils/common_utils'; -import eventHub from '~/notes/event_hub'; import discussionNavigation from '~/notes/mixins/discussion_navigation'; import notesModule from '~/notes/stores/modules'; @@ -35,13 +34,15 @@ describe('Discussion navigation mixin', () => { beforeEach(() => { setHTMLFixture( - [...'abcde'] + `<div class="notes"> + ${[...'abcde'] .map( (id) => `<ul class="notes" data-discussion-id="${id}"></ul> <div class="discussion" data-discussion-id="${id}"></div>`, ) - .join(''), + .join('')} + </div>`, ); jest.spyOn(utils, 'scrollToElementWithContext'); @@ -58,7 +59,7 @@ describe('Discussion navigation mixin', () => { }, diffs: { namespaced: true, - actions: { scrollToFile }, + actions: { scrollToFile, disableVirtualScroller: () => {} }, state: { diffFiles: [] }, }, }, @@ -73,9 +74,6 @@ describe('Discussion navigation mixin', () => { jest.clearAllMocks(); }); - const findDiscussion = (selector, id) => - document.querySelector(`${selector}[data-discussion-id="${id}"]`); - describe('jumpToFirstUnresolvedDiscussion method', () => { let vm; @@ -110,14 +108,14 @@ describe('Discussion navigation mixin', () => { }); describe.each` - fn | args | currentId | expected - ${'jumpToNextDiscussion'} | ${[]} | ${null} | ${'a'} - ${'jumpToNextDiscussion'} | ${[]} | ${'a'} | ${'c'} - ${'jumpToNextDiscussion'} | ${[]} | ${'e'} | ${'a'} - ${'jumpToPreviousDiscussion'} | ${[]} | ${null} | ${'e'} - ${'jumpToPreviousDiscussion'} | ${[]} | ${'e'} | ${'c'} - ${'jumpToPreviousDiscussion'} | ${[]} | ${'c'} | ${'a'} - `('$fn (args = $args, currentId = $currentId)', ({ fn, args, currentId, expected }) => { + fn | args | currentId + ${'jumpToNextDiscussion'} | ${[]} | ${null} + ${'jumpToNextDiscussion'} | ${[]} | ${'a'} + ${'jumpToNextDiscussion'} | ${[]} | ${'e'} + ${'jumpToPreviousDiscussion'} | ${[]} | ${null} + ${'jumpToPreviousDiscussion'} | ${[]} | ${'e'} + ${'jumpToPreviousDiscussion'} | ${[]} | ${'c'} + `('$fn (args = $args, currentId = $currentId)', ({ fn, args, currentId }) => { beforeEach(() => { store.state.notes.currentDiscussionId = currentId; }); @@ -130,125 +128,18 @@ describe('Discussion navigation mixin', () => { await nextTick(); }); - it('expands discussion', () => { - expect(expandDiscussion).toHaveBeenCalled(); - }); - - it('scrolls to element', () => { - expect(utils.scrollToElement).toHaveBeenCalled(); - }); - }); - - describe('on `diffs` active tab', () => { - beforeEach(async () => { - window.mrTabs.currentAction = 'diffs'; - wrapper.vm[fn](...args); - + it('expands discussion', async () => { await nextTick(); - }); - it('sets current discussion', () => { - expect(store.state.notes.currentDiscussionId).toEqual(expected); - }); - - it('expands discussion', () => { expect(expandDiscussion).toHaveBeenCalled(); }); - it('scrolls when scrollToDiscussion is emitted', () => { - expect(utils.scrollToElementWithContext).not.toHaveBeenCalled(); - - eventHub.$emit('scrollToDiscussion'); - - expect(utils.scrollToElementWithContext).toHaveBeenCalledWith( - findDiscussion('ul.notes', expected), - { behavior: 'auto', offset: 0 }, - ); - }); - }); - - describe('on `other` active tab', () => { - beforeEach(async () => { - window.mrTabs.currentAction = 'other'; - wrapper.vm[fn](...args); - + it('scrolls to element', async () => { await nextTick(); - }); - it('sets current discussion', () => { - expect(store.state.notes.currentDiscussionId).toEqual(expected); - }); - - it('does not expand discussion yet', () => { - expect(expandDiscussion).not.toHaveBeenCalled(); - }); - - it('shows mrTabs', () => { - expect(window.mrTabs.tabShown).toHaveBeenCalledWith('show'); - }); - - describe('when tab is changed', () => { - beforeEach(() => { - window.mrTabs.eventHub.$emit('MergeRequestTabChange'); - - jest.runAllTimers(); - }); - - it('expands discussion', () => { - expect(expandDiscussion).toHaveBeenCalledWith(expect.anything(), { - discussionId: expected, - }); - }); - - it('scrolls to discussion', () => { - expect(utils.scrollToElement).toHaveBeenCalledWith( - findDiscussion('div.discussion', expected), - { behavior: 'auto', offset: 0 }, - ); - }); + expect(utils.scrollToElement).toHaveBeenCalled(); }); }); }); - - describe('virtual scrolling feature', () => { - beforeEach(() => { - jest.spyOn(store, 'dispatch'); - - store.state.notes.currentDiscussionId = 'a'; - window.location.hash = 'test'; - }); - - afterEach(() => { - window.gon = {}; - window.location.hash = ''; - }); - - it('resets location hash', async () => { - wrapper.vm.jumpToNextDiscussion(); - - await nextTick(); - - expect(window.location.hash).toBe(''); - }); - - it.each` - tabValue - ${'diffs'} - ${'other'} - `( - 'calls scrollToFile with setHash as $hashValue when the tab is $tabValue', - async ({ tabValue }) => { - window.mrTabs.currentAction = tabValue; - - wrapper.vm.jumpToNextDiscussion(); - - await nextTick(); - - expect(store.dispatch).toHaveBeenCalledWith('diffs/scrollToFile', { - path: 'test.js', - }); - }, - ); - }); }); }); diff --git a/spec/frontend/notes/mock_data.js b/spec/frontend/notes/mock_data.js index 9fa7166474a..286f2adc1d8 100644 --- a/spec/frontend/notes/mock_data.js +++ b/spec/frontend/notes/mock_data.js @@ -1,4 +1,5 @@ // Copied to ee/spec/frontend/notes/mock_data.js +import { __ } from '~/locale'; export const notesDataMock = { discussionsPath: '/gitlab-org/gitlab-foss/issues/26/discussions.json', @@ -35,6 +36,7 @@ export const noteableDataMock = { can_create_note: true, can_update: true, can_award_emoji: true, + can_create_confidential_note: true, }, description: '', due_date: null, @@ -1292,3 +1294,18 @@ export const draftDiffDiscussion = { file_path: 'lib/foo.rb', isDraft: true, }; + +export const notesFilters = [ + { + title: __('Show all activity'), + value: 0, + }, + { + title: __('Show comments only'), + value: 1, + }, + { + title: __('Show history only'), + value: 2, + }, +]; diff --git a/spec/frontend/notes/utils/get_notes_filter_data_spec.js b/spec/frontend/notes/utils/get_notes_filter_data_spec.js new file mode 100644 index 00000000000..c3a8d3bc619 --- /dev/null +++ b/spec/frontend/notes/utils/get_notes_filter_data_spec.js @@ -0,0 +1,44 @@ +import { getNotesFilterData } from '~/notes/utils/get_notes_filter_data'; +import { notesFilters } from '../mock_data'; + +// what: This is the format we expect the element attribute to be in +// why: For readability, we make this clear by hardcoding the indecise instead of using `reduce`. +const TEST_NOTES_FILTERS_ATTR = { + [notesFilters[0].title]: notesFilters[0].value, + [notesFilters[1].title]: notesFilters[1].value, + [notesFilters[2].title]: notesFilters[2].value, +}; + +describe('~/notes/utils/get_notes_filter_data', () => { + it.each([ + { + desc: 'empty', + attributes: {}, + expectation: { + notesFilters: [], + notesFilterValue: undefined, + }, + }, + { + desc: 'valid attributes', + attributes: { + 'data-notes-filters': JSON.stringify(TEST_NOTES_FILTERS_ATTR), + 'data-notes-filter-value': '1', + }, + expectation: { + notesFilters, + notesFilterValue: 1, + }, + }, + ])('with $desc, parses data from element attributes', ({ attributes, expectation }) => { + const el = document.createElement('div'); + + Object.entries(attributes).forEach(([key, value]) => { + el.setAttribute(key, value); + }); + + const actual = getNotesFilterData(el); + + expect(actual).toStrictEqual(expectation); + }); +}); |