Files
nimbus/resources/js/tests/components/domain/Client/Response/ResponseStatus/ResponseStatus.test.ts
Mazen Touati e1b844cee0 feat(history): add history viewer and rewind (#38)
* feat(ui): add `input group` base component

* feat(history): add history viewer and rewind

* test: update selector snapshot

* test: add PW base page

* style: apply TS style fixes

* chore(history): request history wiki

* chore(history): remove unwanted symbol

* chore: fix type

* style: apply TS style fixes
2026-01-17 20:50:00 +01:00

235 lines
7.5 KiB
TypeScript

import ResponseStatus from '@/components/domain/Client/Response/ResponseStatus/ResponseStatus.vue';
import { RequestLog } from '@/interfaces';
import { AuthorizationType } from '@/interfaces/generated';
import { PendingRequest, Request, RequestBodyTypeEnum, STATUS } from '@/interfaces/http';
import { renderWithProviders, screen } from '@/tests/_utils/test-utils';
import { fireEvent } from '@testing-library/vue';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { nextTick, Reactive, reactive } from 'vue';
const mockRequestStore: Reactive<{
pendingRequestData: Partial<PendingRequest> | null;
cancelCurrentRequest: Mock<() => void>;
restoreFromHistory: Mock<(request: Request) => void>;
}> = reactive({
pendingRequestData: null,
cancelCurrentRequest: vi.fn(),
restoreFromHistory: vi.fn(),
});
const mockRequestsHistoryStore: Reactive<{
lastLog: RequestLog | null;
allLogs: RequestLog[];
setActiveLog: Mock<(index: number) => void>;
}> = reactive({
lastLog: null,
allLogs: [],
setActiveLog: vi.fn(),
});
vi.mock('@/stores', async importOriginal => {
const actual = await importOriginal<object>();
return {
...actual,
useRequestStore: () => mockRequestStore,
useRequestsHistoryStore: () => mockRequestsHistoryStore,
};
});
describe('ResponseStatus', () => {
beforeEach(() => {
mockRequestStore.pendingRequestData = null;
mockRequestsHistoryStore.lastLog = null;
mockRequestsHistoryStore.allLogs = [];
mockRequestStore.cancelCurrentRequest.mockClear();
mockRequestStore.restoreFromHistory.mockClear();
mockRequestsHistoryStore.setActiveLog.mockClear();
});
it('shows pending status and cancel option while processing', async () => {
mockRequestStore.pendingRequestData = { isProcessing: true, durationInMs: 1234 };
renderWithProviders(ResponseStatus);
expect(screen.queryByTestId('response-badge')).toBeNull();
expect(screen.getByTestId('response-status-indicator')).toBeInTheDocument();
expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
});
it('shows empty status when nothing processed yet', async () => {
mockRequestStore.pendingRequestData = {
isProcessing: false,
durationInMs: 0,
wasExecuted: false,
};
renderWithProviders(ResponseStatus);
await nextTick();
expect(screen.getByTestId('response-status-text')).toHaveTextContent(
String(STATUS.EMPTY),
);
});
it('derives status details from last successful log', async () => {
mockRequestStore.pendingRequestData = {
isProcessing: false,
wasExecuted: true,
};
const mockRequest: Request = {
method: 'GET',
endpoint: '/api/test',
headers: [],
queryParameters: [],
body: null,
payloadType: RequestBodyTypeEnum.EMPTY,
authorization: { type: AuthorizationType.None },
routeDefinition: {
method: 'GET',
endpoint: '/api/test',
shortEndpoint: '/api/test',
schema: { shape: {}, extractionErrors: null },
},
};
mockRequestsHistoryStore.lastLog = {
durationInMs: 3000,
isProcessing: false,
request: mockRequest,
response: {
status: STATUS.SUCCESS,
statusCode: 201,
statusText: 'Created',
sizeInBytes: 4096,
timestamp: Math.floor(Date.now() / 1000),
body: '',
headers: [],
cookies: [],
},
};
renderWithProviders(ResponseStatus);
await nextTick();
expect(screen.getByTestId('response-status-badge')).toHaveTextContent(
'201 - Created',
);
expect(screen.getByTestId('response-status-size')).toHaveTextContent('4.1kB');
expect(screen.getByTestId('response-status-duration')).toHaveTextContent('3.00s');
});
it('resets size to zero when request was not executed', async () => {
mockRequestStore.pendingRequestData = {
isProcessing: false,
wasExecuted: false,
durationInMs: 0,
};
const mockRequest: Request = {
method: 'GET',
endpoint: '/api/test',
headers: [],
queryParameters: [],
body: null,
payloadType: RequestBodyTypeEnum.EMPTY,
authorization: { type: AuthorizationType.None },
routeDefinition: {
method: 'GET',
endpoint: '/api/test',
shortEndpoint: '/api/test',
schema: { shape: {}, extractionErrors: null },
},
};
mockRequestsHistoryStore.lastLog = {
durationInMs: 0,
isProcessing: false,
request: mockRequest,
response: {
status: STATUS.SUCCESS,
statusCode: 200,
statusText: 'OK',
body: '',
headers: [],
cookies: [],
sizeInBytes: 12345,
timestamp: Math.floor(Date.now() / 1000),
},
};
renderWithProviders(ResponseStatus);
await nextTick();
expect(screen.getByText(/0B/)).toBeInTheDocument();
});
it('cancels request when cancel button clicked', async () => {
mockRequestStore.pendingRequestData = { isProcessing: true, durationInMs: 0 };
renderWithProviders(ResponseStatus);
await nextTick();
await fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
expect(mockRequestStore.cancelCurrentRequest).toHaveBeenCalled();
});
it('opens history dropdown and selects an item', async () => {
const log: RequestLog = {
durationInMs: 100,
isProcessing: false,
request: {
method: 'POST',
endpoint: 'test',
headers: [],
queryParameters: [],
body: null,
payloadType: RequestBodyTypeEnum.EMPTY,
authorization: { type: AuthorizationType.None },
routeDefinition: {
method: 'POST',
endpoint: 'test',
shortEndpoint: 'test',
schema: { shape: {}, extractionErrors: null },
},
},
response: {
status: STATUS.SUCCESS,
statusCode: 200,
statusText: 'OK',
timestamp: Math.floor(Date.now() / 1000),
body: '{}',
headers: [],
cookies: [],
sizeInBytes: 10,
},
};
mockRequestsHistoryStore.allLogs = [log];
mockRequestsHistoryStore.lastLog = log;
mockRequestStore.pendingRequestData = { wasExecuted: true };
renderWithProviders(ResponseStatus);
await nextTick();
const trigger = screen.getByTestId('response-history-trigger');
await fireEvent.click(trigger);
// We can't easily test Radix dropdown content with testing-library-vue without more setup,
// but we can verify the trigger is there and clickable.
// For a more thorough test, we would need to mock the dropdown portal or use Playwright.
expect(trigger).toBeInTheDocument();
});
});