import { render, screen, fireEvent } from '@testing-library/vue' import '@testing-library/jest-dom' import badges from './badges.vue' const localVue = global.localVue describe('badge settings', () => { let mocks const apolloMutateMock = jest.fn() const Wrapper = () => { return render(badges, { localVue, mocks, }) } beforeEach(() => { apolloMutateMock.mockReset() mocks = { $t: jest.fn((t) => t), $toast: { success: jest.fn(), error: jest.fn(), info: jest.fn(), }, $apollo: { mutate: apolloMutateMock, }, } }) describe('without badges', () => { beforeEach(() => { mocks.$store = { getters: { 'auth/isModerator': () => false, 'auth/user': { id: 'u23', badgeVerification: { id: 'bv1', icon: '/verification/icon', description: 'Verification description', isDefault: true, }, badgeTrophiesSelected: [], badgeTrophiesUnused: [], }, }, } }) it('renders', () => { const wrapper = Wrapper() expect(wrapper.container).toMatchSnapshot() }) }) describe('with badges', () => { const badgeTrophiesSelected = [ { id: '1', icon: '/path/to/some/icon', isDefault: false, description: 'Some description', }, { id: '2', icon: '/path/to/empty/icon', isDefault: true, description: 'Empty', }, { id: '3', icon: '/path/to/third/icon', isDefault: false, description: 'Third description', }, ] const badgeTrophiesUnused = [ { id: '4', icon: '/path/to/fourth/icon', description: 'Fourth description', }, { id: '5', icon: '/path/to/fifth/icon', description: 'Fifth description', }, ] let wrapper beforeEach(() => { mocks.$store = { commit: jest.fn(), getters: { 'auth/isModerator': () => false, 'auth/user': { id: 'u23', badgeVerification: { id: 'bv1', icon: '/verification/icon', description: 'Verification description', isDefault: false, }, badgeTrophiesSelected, badgeTrophiesUnused, }, }, } wrapper = Wrapper() }) it('renders', () => { expect(wrapper.container).toMatchSnapshot() }) describe('selecting a used badge', () => { beforeEach(async () => { const badge = screen.getByTitle(badgeTrophiesSelected[0].description) await fireEvent.click(badge) }) it('shows remove badge button', () => { expect(screen.getByText('settings.badges.remove')).toBeInTheDocument() }) describe('clicking remove badge button', () => { const clickButton = async () => { const removeButton = screen.getByText('settings.badges.remove') await fireEvent.click(removeButton) } describe('with successful server request', () => { const removedResponseData = { setTrophyBadgeSelected: { id: 'u23', badgeTrophiesSelected: [ { id: 'empty-0', icon: '/path/to/empty/icon', isDefault: true, description: 'Empty', }, { id: '2', icon: '/path/to/empty/icon', isDefault: true, description: 'Empty', }, { id: '3', icon: '/path/to/third/icon', isDefault: false, description: 'Third description', }, ], badgeTrophiesUnused: [ { id: '1', icon: '/path/to/some/icon', description: 'Some description', }, { id: '4', icon: '/path/to/fourth/icon', description: 'Fourth description', }, { id: '5', icon: '/path/to/fifth/icon', description: 'Fifth description', }, ], }, } beforeEach(async () => { apolloMutateMock.mockImplementation(({ update }) => { const result = { data: removedResponseData } if (update) update(null, result) return Promise.resolve(result) }) await clickButton() }) it('calls the server', () => { expect(apolloMutateMock).toHaveBeenCalledWith({ mutation: expect.anything(), update: expect.anything(), variables: { badgeId: null, slot: 0, }, }) }) it('updates badges in store via update callback', () => { expect(mocks.$store.commit).toHaveBeenCalledWith( 'auth/SET_USER', expect.objectContaining({ id: 'u23', badgeTrophiesSelected: removedResponseData.setTrophyBadgeSelected.badgeTrophiesSelected, badgeTrophiesUnused: removedResponseData.setTrophyBadgeSelected.badgeTrophiesUnused, }), ) }) it('shows a success message', () => { expect(mocks.$toast.success).toHaveBeenCalledWith('settings.badges.success-update') }) }) describe('with failed server request', () => { beforeEach(async () => { apolloMutateMock.mockRejectedValue({ message: 'Ouch!' }) await clickButton() }) it('shows an error message', () => { expect(mocks.$toast.error).toHaveBeenCalledWith('settings.badges.error-update') }) }) }) }) describe('no more badges available', () => { beforeEach(async () => { mocks.$store.getters['auth/user'].badgeTrophiesUnused = [] }) describe('selecting an empty slot', () => { beforeEach(async () => { const emptySlot = screen.getAllByTitle('Empty')[0] await fireEvent.click(emptySlot) }) it('shows no more badges available message', () => { expect(wrapper.container).toMatchSnapshot() }) }) }) describe('more badges available', () => { describe('selecting an empty slot', () => { beforeEach(async () => { const emptySlot = screen.getAllByTitle('Empty')[0] await fireEvent.click(emptySlot) }) it('shows list with available badges', () => { expect(wrapper.container).toMatchSnapshot() }) describe('clicking on an available badge', () => { const clickBadge = async () => { const badge = screen.getByText(badgeTrophiesUnused[0].description) await fireEvent.click(badge) } describe('with successful server request', () => { const addedResponseData = { setTrophyBadgeSelected: { id: 'u23', badgeTrophiesSelected: [ { id: '1', icon: '/path/to/some/icon', isDefault: false, description: 'Some description', }, { id: '4', icon: '/path/to/fourth/icon', isDefault: false, description: 'Fourth description', }, { id: '3', icon: '/path/to/third/icon', isDefault: false, description: 'Third description', }, ], }, } beforeEach(async () => { apolloMutateMock.mockImplementation(({ update }) => { const result = { data: addedResponseData } if (update) update(null, result) return Promise.resolve(result) }) await clickBadge() }) it('calls the server', () => { expect(apolloMutateMock).toHaveBeenCalledWith({ mutation: expect.anything(), update: expect.anything(), variables: { badgeId: '4', slot: 1, }, }) }) it('updates badges in store via update callback', () => { expect(mocks.$store.commit).toHaveBeenCalledWith( 'auth/SET_USER', expect.objectContaining({ id: 'u23', badgeTrophiesSelected: addedResponseData.setTrophyBadgeSelected.badgeTrophiesSelected, }), ) }) it('shows a success message', () => { expect(mocks.$toast.success).toHaveBeenCalledWith('settings.badges.success-update') }) }) describe('with failed server request', () => { beforeEach(async () => { apolloMutateMock.mockRejectedValue({ message: 'Ouch!' }) await clickBadge() }) it('shows an error message', () => { expect(mocks.$toast.error).toHaveBeenCalledWith('settings.badges.error-update') }) }) }) }) }) describe('drag and drop', () => { const makeDropData = (source) => JSON.stringify(source) describe('assign badge from reserve to empty slot via DnD', () => { const assignResponseData = { setTrophyBadgeSelected: { id: 'u23', badgeTrophiesSelected: [ badgeTrophiesSelected[0], { id: '4', icon: '/path/to/fourth/icon', isDefault: false, description: 'Fourth description', }, badgeTrophiesSelected[2], ], badgeTrophiesUnused: [badgeTrophiesUnused[1]], }, } beforeEach(async () => { apolloMutateMock.mockImplementation(({ update }) => { const result = { data: assignResponseData } if (update) update(null, result) return Promise.resolve(result) }) // Simulate dropping a reserve badge on the empty hex slot (index 2 in Badges = slot 1) const emptySlot = screen.getAllByTitle('Empty')[0] const container = emptySlot.closest('.hc-badge-container') const sourceData = makeDropData({ source: 'reserve', badge: badgeTrophiesUnused[0], }) await fireEvent.drop(container, { dataTransfer: { getData: () => sourceData }, }) }) it('calls the server with correct badge and slot', () => { expect(apolloMutateMock).toHaveBeenCalledWith( expect.objectContaining({ variables: { badgeId: '4', slot: 1, }, }), ) }) it('shows success message', () => { expect(mocks.$toast.success).toHaveBeenCalledWith('settings.badges.success-update') }) }) describe('remove badge from hex to reserve via DnD', () => { const removeResponseData = { setTrophyBadgeSelected: { id: 'u23', badgeTrophiesSelected: [ { id: 'empty-0', icon: '/path/to/empty/icon', isDefault: true, description: 'Empty', }, badgeTrophiesSelected[1], badgeTrophiesSelected[2], ], badgeTrophiesUnused: [badgeTrophiesSelected[0], ...badgeTrophiesUnused], }, } beforeEach(async () => { apolloMutateMock.mockImplementation(({ update }) => { const result = { data: removeResponseData } if (update) update(null, result) return Promise.resolve(result) }) // Simulate dropping a hex badge on the reserve container const reserveContainer = wrapper.container.querySelector('.badge-selection') const hexData = makeDropData({ source: 'hex', index: 1, badge: badgeTrophiesSelected[0], }) await fireEvent.drop(reserveContainer, { dataTransfer: { getData: () => hexData }, }) }) it('calls the server to remove badge', () => { expect(apolloMutateMock).toHaveBeenCalledWith( expect.objectContaining({ variables: { badgeId: null, slot: 0, }, }), ) }) it('shows success message', () => { expect(mocks.$toast.success).toHaveBeenCalledWith('settings.badges.success-update') }) }) describe('swap two badges via DnD', () => { const swapResponse1 = { setTrophyBadgeSelected: { id: 'u23', badgeTrophiesSelected: [ { id: '3', icon: '/path/to/third/icon', isDefault: false, description: 'Third description', }, badgeTrophiesSelected[1], { id: 'empty-temp', icon: '/path/to/empty/icon', isDefault: true, description: 'Empty', }, ], badgeTrophiesUnused: [badgeTrophiesSelected[0], ...badgeTrophiesUnused], }, } const swapResponse2 = { setTrophyBadgeSelected: { id: 'u23', badgeTrophiesSelected: [ { id: '3', icon: '/path/to/third/icon', isDefault: false, description: 'Third description', }, badgeTrophiesSelected[1], { id: '1', icon: '/path/to/some/icon', isDefault: false, description: 'Some description', }, ], badgeTrophiesUnused, }, } beforeEach(async () => { let callCount = 0 apolloMutateMock.mockImplementation(({ update }) => { callCount++ const responseData = callCount === 1 ? swapResponse1 : swapResponse2 const result = { data: responseData } if (update) update(null, result) return Promise.resolve(result) }) // Simulate dragging badge at index 1 onto badge at index 3 (both occupied) const targetBadge = screen.getByTitle(badgeTrophiesSelected[2].description) const container = targetBadge.closest('.hc-badge-container') const sourceData = makeDropData({ source: 'hex', index: 1, badge: badgeTrophiesSelected[0], }) await fireEvent.drop(container, { dataTransfer: { getData: () => sourceData }, }) }) it('calls the server twice for swap', () => { expect(apolloMutateMock).toHaveBeenCalledTimes(2) }) it('first mutation moves source to target slot', () => { expect(apolloMutateMock).toHaveBeenNthCalledWith( 1, expect.objectContaining({ variables: { badgeId: '1', slot: 2, }, }), ) }) it('second mutation moves former target to source slot', () => { expect(apolloMutateMock).toHaveBeenNthCalledWith( 2, expect.objectContaining({ variables: { badgeId: '3', slot: 0, }, }), ) }) it('shows success message', () => { expect(mocks.$toast.success).toHaveBeenCalledWith('settings.badges.success-update') }) }) describe('swap with partial failure', () => { beforeEach(async () => { let callCount = 0 apolloMutateMock.mockImplementation(({ update }) => { callCount++ if (callCount === 1) { const result = { data: { setTrophyBadgeSelected: { id: 'u23', badgeTrophiesSelected: badgeTrophiesSelected, badgeTrophiesUnused: badgeTrophiesUnused, }, }, } if (update) update(null, result) return Promise.resolve(result) } return Promise.reject(new Error('Server error')) }) const targetBadge = screen.getByTitle(badgeTrophiesSelected[2].description) const container = targetBadge.closest('.hc-badge-container') const sourceData = makeDropData({ source: 'hex', index: 1, badge: badgeTrophiesSelected[0], }) await fireEvent.drop(container, { dataTransfer: { getData: () => sourceData }, }) }) it('shows swap partial error', () => { expect(mocks.$toast.error).toHaveBeenCalledWith('settings.badges.swap-partial-error') }) }) }) }) })