RewardTrackAdminView.test.tsx 11.2 Ko
Télécharger le fichier
import { cleanup, fireEvent, render, screen, within } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { localInputToUnix, RewardTrackAdminView, unixToLocalInput } from './RewardTrackAdminView';
import { selectOption } from '../../test-utils/selectOption';
const mocks = vi.hoisted(() => ({
saveTrack: vi.fn(),
saveTask: vi.fn(),
savePrize: vi.fn(),
saveTexts: vi.fn(),
searchFurni: vi.fn(),
deleteEntity: vi.fn(),
requestData: vi.fn(),
state: {
loaded: true,
pending: false,
lastResult: null,
actionTypes: [ 'chat_with_someone', 'place_item' ],
rewardTypes: [ 'duckets', 'badge', 'furni' ],
tracks: [
{
id: 'season_1',
theme: 'blue',
sortOrder: 0,
startsAt: 0,
endsAt: 0,
hasPremium: false,
premiumBoostPercent: 100,
premiumInstantPoints: 0,
premiumCostDiamonds: 0,
premiumCostCredits: 0,
enabled: true,
tasks: [
{ id: 'talk', actionType: 'chat_with_someone', parameter: '', premium: false, sortOrder: 1, levels: [ { requiredCount: 2, pointsReward: 10, premium: false } ] },
{ id: 'visit', actionType: 'place_item', parameter: '', premium: false, sortOrder: 2, levels: [ { requiredCount: 3, pointsReward: 10, premium: false } ] }
],
prizes: [ { id: 'p1', requiredPoints: 10, productItemTypeId: 0, rewardType: 'duckets', extraParams: '', rewardAmount: 50, premium: false, sortOrder: 1, claimedCount: 12 } ],
texts: { name: 'Season one' }
},
{ id: 'old_one', theme: 'red', sortOrder: 1, startsAt: 0, endsAt: 1_000_000, hasPremium: false, premiumBoostPercent: 100, premiumInstantPoints: 0, premiumCostDiamonds: 0, premiumCostCredits: 0, enabled: true, tasks: [], prizes: [], texts: {} }
]
}
}));
vi.mock('../../api', async () => {
const rules = await import('../../api/quests/RewardTrackAdminRules');
return { ...rules, localizeWithFallback: (_key: string, fallback: string) => fallback };
});
vi.mock('../../hooks', () => ({
useRewardTrackAdmin: () => ({
...mocks.state,
requestData: mocks.requestData,
saveTrack: mocks.saveTrack,
saveTask: mocks.saveTask,
savePrize: mocks.savePrize,
saveTexts: mocks.saveTexts,
searchFurni: mocks.searchFurni,
furniSearch: null,
deleteEntity: mocks.deleteEntity
})
}));
afterEach(() => {
cleanup();
vi.clearAllMocks();
});
/** Renders the editor and unfolds both panels, which start folded. */
const renderOpen = (onPreview?: (id: string) => void) => {
const result = render(<RewardTrackAdminView onClose={() => null} onPreview={onPreview} />);
fireEvent.click(screen.getByTestId('rt-admin-collapse-tasks'));
fireEvent.click(screen.getByTestId('rt-admin-collapse-prizes'));
return result;
};
describe('RewardTrackAdminView', () => {
it('asks for the data once, starts with the panels folded and lists the rows once unfolded', () => {
render(<RewardTrackAdminView onClose={() => null} />);
expect(mocks.requestData).toHaveBeenCalledTimes(1);
expect(screen.queryAllByTestId('rt-admin-task-row')).toHaveLength(0);
expect(screen.getByText('talk · visit')).toBeTruthy();
fireEvent.click(screen.getByTestId('rt-admin-collapse-tasks'));
fireEvent.click(screen.getByTestId('rt-admin-collapse-prizes'));
expect(screen.getAllByTestId('rt-admin-task-row')).toHaveLength(2);
expect(screen.getAllByTestId('rt-admin-prize-row')).toHaveLength(1);
});
it('folds a panel from its chevron and from the rest of its head, once per click', () => {
render(<RewardTrackAdminView onClose={() => null} />);
const chevron = screen.getByTestId('rt-admin-collapse-tasks');
expect(chevron.getAttribute('aria-expanded')).toBe('false');
fireEvent.click(chevron);
expect(chevron.getAttribute('aria-expanded')).toBe('true');
expect(screen.getAllByTestId('rt-admin-task-row')).toHaveLength(2);
// The head (not only the chevron) folds the panel too.
fireEvent.click(chevron.parentElement as HTMLElement);
expect(chevron.getAttribute('aria-expanded')).toBe('false');
expect(screen.queryAllByTestId('rt-admin-task-row')).toHaveLength(0);
});
it('edits a task, adds a level and sends the whole task back', () => {
renderOpen();
fireEvent.click(screen.getAllByText('Edit')[1]);
const form = screen.getByTestId('rt-admin-task-form');
expect(screen.getAllByTestId('rt-admin-level-row')).toHaveLength(1);
fireEvent.click(screen.getByText('Add level'));
const rows = screen.getAllByTestId('rt-admin-level-row');
expect(rows).toHaveLength(2);
const [requiredCount, pointsReward] = within(rows[1]).getAllByRole('spinbutton');
fireEvent.change(requiredCount, { target: { value: '5' } });
fireEvent.change(pointsReward, { target: { value: '25' } });
fireEvent.click(within(rows[1]).getByRole('checkbox'));
fireEvent.click(within(form).getByText('Save'));
expect(mocks.saveTask).toHaveBeenCalledWith({
trackId: 'season_1',
id: 'talk',
actionType: 'chat_with_someone',
parameter: '',
premium: false,
sortOrder: 1,
levels: [
{ requiredCount: 2, pointsReward: 10, premium: false },
{ requiredCount: 5, pointsReward: 25, premium: true }
]
});
});
it('points at a broken level and keeps Save closed until it is fixed', () => {
renderOpen();
fireEvent.click(screen.getAllByText('Edit')[1]);
fireEvent.click(screen.getByText('Add level'));
const rows = screen.getAllByTestId('rt-admin-level-row');
fireEvent.change(rows[1].querySelectorAll('input')[0], { target: { value: '1' } });
const form = screen.getByTestId('rt-admin-task-form');
expect(within(form).getByText('More than the level before')).toBeTruthy();
expect(screen.getByTestId('rt-admin-form-problems').textContent).toBe('1 field to fix');
fireEvent.click(within(form).getByText('Save'));
expect(mocks.saveTask).not.toHaveBeenCalled();
});
it('moves a task down by saving the two tasks that swap places', () => {
renderOpen();
fireEvent.click(screen.getAllByTestId('rt-admin-task-down')[0]);
expect(mocks.saveTask).toHaveBeenCalledTimes(2);
expect(mocks.saveTask.mock.calls[0][0]).toMatchObject({ id: 'visit', sortOrder: 1 });
expect(mocks.saveTask.mock.calls[1][0]).toMatchObject({ id: 'talk', sortOrder: 2 });
});
it('duplicates a track with its tasks and prizes under the new id', () => {
renderOpen();
fireEvent.click(screen.getByTestId('rt-admin-duplicate-track'));
const form = screen.getByTestId('rt-admin-track-form');
const idInput = form.querySelector('input') as HTMLInputElement;
expect(idInput.value).toBe('season_1_copy');
fireEvent.change(idInput, { target: { value: 'season_2' } });
fireEvent.click(within(form).getByText('Save'));
expect(mocks.saveTrack).toHaveBeenCalledWith(expect.objectContaining({ id: 'season_2', enabled: false }));
expect(mocks.saveTask).toHaveBeenCalledTimes(2);
expect(mocks.saveTask.mock.calls[0][0]).toMatchObject({ trackId: 'season_2', id: 'talk' });
expect(mocks.savePrize).toHaveBeenCalledWith(expect.objectContaining({ trackId: 'season_2', id: 'p1' }));
});
it('offers the twin of a prize on the other row, 20 points further', () => {
renderOpen();
fireEvent.click(screen.getByTestId('rt-admin-prize-twin'));
const form = screen.getByTestId('rt-admin-prize-form');
const inputs = form.querySelectorAll('input');
expect((inputs[0] as HTMLInputElement).value).toBe('p1_premium');
expect((inputs[1] as HTMLInputElement).value).toBe('30');
fireEvent.click(within(form).getByText('Save'));
expect(mocks.savePrize).toHaveBeenCalledWith(expect.objectContaining({ id: 'p1_premium', premium: true, requiredPoints: 30 }));
});
it('hands the selected track to the preview', () => {
const onPreview = vi.fn();
renderOpen(onPreview);
fireEvent.click(screen.getByTestId('rt-admin-preview'));
expect(onPreview).toHaveBeenCalledWith('season_1');
});
it('deletes only on the second click', () => {
renderOpen();
const deleteButtons = screen.getAllByTestId('rt-admin-delete');
fireEvent.click(deleteButtons[3]);
expect(mocks.deleteEntity).not.toHaveBeenCalled();
fireEvent.click(screen.getByTestId('rt-admin-delete-confirm'));
expect(mocks.deleteEntity).toHaveBeenCalledWith('prize', 'season_1', 'p1');
});
it('filters the track list by state and shows the claimed count of a prize', () => {
renderOpen();
expect(screen.getAllByTestId('rt-admin-track-item')).toHaveLength(2);
expect(screen.getByTestId('rt-admin-prize-claimed').textContent).toBe('12');
fireEvent.click(within(screen.getByTestId('rt-admin-filters')).getByText('expired'));
expect(screen.getAllByTestId('rt-admin-track-item')).toHaveLength(1);
expect(screen.getByTestId('rt-admin-track-item').textContent).toContain('old_one');
fireEvent.click(within(screen.getByTestId('rt-admin-filters')).getByText('active'));
expect(screen.getByTestId('rt-admin-track-item').textContent).toContain('Season one (season_1)');
});
it('edits the texts of the track and its tasks and sends the filled ones', () => {
renderOpen();
fireEvent.click(screen.getByTestId('rt-admin-texts'));
const form = screen.getByTestId('rt-admin-texts-form');
expect((screen.getByTestId('rt-admin-text-name') as HTMLInputElement).value).toBe('Season one');
fireEvent.change(screen.getByTestId('rt-admin-text-task.talk.desc'), { target: { value: 'Chat with other Habbos' } });
fireEvent.click(within(form).getByText('Save'));
expect(mocks.saveTexts).toHaveBeenCalledWith('season_1', [
{ key: 'name', value: 'Season one' },
{ key: 'task.talk.desc', value: 'Chat with other Habbos' }
]);
});
it('offers the furni search on a furni prize and puts the pick in the name field', () => {
renderOpen();
fireEvent.click(screen.getByText('New prize'));
const form = screen.getByTestId('rt-admin-prize-form');
expect(screen.queryByTestId('rt-admin-furni-picker')).toBeNull();
selectOption(within(form).getByRole('combobox'), 'furni');
expect(screen.getByTestId('rt-admin-furni-picker')).toBeTruthy();
});
it('converts the track dates both ways and keeps 0 for an empty date', () => {
expect(unixToLocalInput(0)).toBe('');
expect(localInputToUnix('')).toBe(0);
const seconds = 1_800_000_000;
expect(localInputToUnix(unixToLocalInput(seconds))).toBe(seconds);
});
});
