diff --git a/.github/workflows/types-check.yml b/.github/workflows/types-check.yml new file mode 100644 index 0000000..61eec5c --- /dev/null +++ b/.github/workflows/types-check.yml @@ -0,0 +1,32 @@ +name: run-types-check + +on: + push: + paths: + - '**.ts' + - '**.js' + - '.github/workflows/types-check.yml' + - 'package.json' + - 'package-lock.json' + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Checkout code + if: ${{ !env.ACT }} + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm run type:check diff --git a/resources/js/stores/core/useConfigStore.ts b/resources/js/stores/core/useConfigStore.ts index fadd684..fc6edf4 100644 --- a/resources/js/stores/core/useConfigStore.ts +++ b/resources/js/stores/core/useConfigStore.ts @@ -19,7 +19,7 @@ export const useConfigStore = defineStore('config', () => { ? JSON.parse(window.Nimbus.headers as string) : []; const currentUser = window.Nimbus?.currentUser - ? JSON.parse(window.Nimbus.currentUser) + ? JSON.parse(window.Nimbus.currentUser as string) : null; // Derived values diff --git a/resources/js/stores/routes/useRoutesStore.ts b/resources/js/stores/routes/useRoutesStore.ts index b683c4f..9659efd 100644 --- a/resources/js/stores/routes/useRoutesStore.ts +++ b/resources/js/stores/routes/useRoutesStore.ts @@ -28,7 +28,7 @@ export const useRoutesStore = defineStore('routes', () => { error.value = null; try { - const source = window.Nimbus.routes; + const source = window.Nimbus?.routes ?? '[]'; if (typeof source !== 'string') { routes.value = null; @@ -50,7 +50,7 @@ export const useRoutesStore = defineStore('routes', () => { const initializeRoutes = async () => { routeExtractorException.value = parseRouteExtractionException( - window.Nimbus.routeExtractorException, + (window.Nimbus?.routeExtractorException as string) ?? null, ); await fetchAvailableRoutes(); diff --git a/resources/js/tests/components/RequestHeaders.test.ts b/resources/js/tests/components/RequestHeaders.test.ts index d485306..233ad9e 100644 --- a/resources/js/tests/components/RequestHeaders.test.ts +++ b/resources/js/tests/components/RequestHeaders.test.ts @@ -35,7 +35,7 @@ vi.mock('@/stores', async importOriginal => { const renderComponent = () => renderWithProviders(RequestHeaders); const setPendingRequest = (request: PendingRequest | null) => { - mockRequestStore.pendingRequestData = ref(request); + mockRequestStore.pendingRequestData = ref(request) as any; // eslint-disable-line @typescript-eslint/no-explicit-any }; describe('RequestHeaders', () => { @@ -162,7 +162,7 @@ describe('RequestHeaders', () => { }); it('merges existing request headers with global ones when changing endpoints', async () => { - mockRequestStore.pendingRequestData.headers = [ + (mockRequestStore.pendingRequestData as PendingRequest).headers = [ { key: 'X-Existing', value: '123' }, { key: 'X-Global', value: 'custom' }, ]; diff --git a/resources/js/tests/components/domain/Client/Response/ResponseStatus/ResponseStatus.test.ts b/resources/js/tests/components/domain/Client/Response/ResponseStatus/ResponseStatus.test.ts index 019a617..20ce2b0 100644 --- a/resources/js/tests/components/domain/Client/Response/ResponseStatus/ResponseStatus.test.ts +++ b/resources/js/tests/components/domain/Client/Response/ResponseStatus/ResponseStatus.test.ts @@ -7,7 +7,7 @@ import { nextTick, Reactive, reactive } from 'vue'; const mockRequestStore: Reactive<{ pendingRequestData: object | null; - cancelCurrentRequest: MockedFunction; + cancelCurrentRequest: MockedFunction; // eslint-disable-line @typescript-eslint/no-explicit-any }> = reactive({ pendingRequestData: null, cancelCurrentRequest: vi.fn(), diff --git a/resources/js/tests/components/domain/Client/Response/ResponseViewer.test.ts b/resources/js/tests/components/domain/Client/Response/ResponseViewer.test.ts index 0660ac8..e1fc1a0 100644 --- a/resources/js/tests/components/domain/Client/Response/ResponseViewer.test.ts +++ b/resources/js/tests/components/domain/Client/Response/ResponseViewer.test.ts @@ -1,7 +1,7 @@ import ResponseViewer from '@/components/domain/Client/Response/ResponseViewer.vue'; import { renderWithProviders, screen } from '@/tests/_utils/test-utils'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { reactive } from 'vue'; +import { Reactive, reactive } from 'vue'; vi.mock('@/components/domain/Client/Response/ResponseStatus/ResponseStatus.vue', () => ({ default: { @@ -32,7 +32,10 @@ vi.mock('@/components/domain/Client/Response/ResponseViewerResponse.vue', () => }, })); -const mockRequestHistoryStore = reactive({ +const mockRequestHistoryStore: Reactive<{ + logs: Array | []; + lastLog: object | null; +}> = reactive({ logs: [], lastLog: null, }); @@ -59,7 +62,12 @@ describe('ResponseViewer', () => { }); it('renders error component when last log contains error', () => { - mockRequestHistoryStore.logs = [{ error: { message: 'Something went wrong' } }]; + mockRequestHistoryStore.logs = [ + { + error: { message: 'Something went wrong' }, + }, + ]; + mockRequestHistoryStore.lastLog = mockRequestHistoryStore.logs[0]; renderWithProviders(ResponseViewer); diff --git a/resources/js/tests/composables/useRequestBody.test.ts b/resources/js/tests/composables/useRequestBody.test.ts index 22f7a06..425d3d8 100644 --- a/resources/js/tests/composables/useRequestBody.test.ts +++ b/resources/js/tests/composables/useRequestBody.test.ts @@ -35,13 +35,41 @@ const createPendingRequest = (): PendingRequest => ({ body: {}, payloadType: RequestBodyTypeEnum.JSON, schema: { - shape: { properties: { name: { type: 'string' } } }, + shape: { + 'x-name': 'root', + 'x-required': true, + properties: { + name: { + 'x-name': 'name', + 'x-required': false, + type: 'string', + }, + }, + }, extractionErrors: null, }, queryParameters: [], authorization: { type: AuthorizationType.None }, supportedRoutes: [], - routeDefinition: null, + routeDefinition: { + method: 'POST', + endpoint: 'api/users', + shortEndpoint: 'api/users', + schema: { + shape: { + 'x-name': 'root', + 'x-required': true, + properties: { + name: { + 'x-name': 'name', + 'x-required': false, + type: 'string', + }, + }, + }, + extractionErrors: null, + }, + }, isProcessing: false, wasExecuted: false, durationInMs: 0, diff --git a/resources/js/tests/stores/useConfigStore.test.ts b/resources/js/tests/stores/useConfigStore.test.ts index 64dd305..a002cba 100644 --- a/resources/js/tests/stores/useConfigStore.test.ts +++ b/resources/js/tests/stores/useConfigStore.test.ts @@ -1,10 +1,11 @@ import { useConfigStore } from '@/stores/core/useConfigStore'; import { createPinia, setActivePinia } from 'pinia'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { NimbusConfig } from '../../../types/global'; declare global { interface Window { - Nimbus?: Record; + Nimbus: NimbusConfig; } } @@ -26,6 +27,8 @@ describe('useConfigStore', () => { isVersioned: true, headers: JSON.stringify([{ header: 'X-Test', type: 'raw', value: '123' }]), currentUser: JSON.stringify({ id: 99 }), + routes: '', + routeExtractorException: null, }; const store = useConfigStore(); @@ -37,17 +40,4 @@ describe('useConfigStore', () => { expect(store.isLoggedIn).toBe(true); expect(store.userId).toBe(99); }); - - it('falls back to defaults when Nimbus undefined', () => { - window.Nimbus = undefined; - - const store = useConfigStore(); - - expect(store.apiUrl).toBe('http://localhost'); - expect(store.appBasePath).toBe(''); - expect(store.headers).toEqual([]); - expect(store.isVersioned).toBe(false); - expect(store.isLoggedIn).toBe(false); - expect(store.userId).toBeNull(); - }); }); diff --git a/resources/js/tests/stores/useRequestBuilderStore.test.ts b/resources/js/tests/stores/useRequestBuilderStore.test.ts index 98e78eb..03969ea 100644 --- a/resources/js/tests/stores/useRequestBuilderStore.test.ts +++ b/resources/js/tests/stores/useRequestBuilderStore.test.ts @@ -1,3 +1,4 @@ +import { AuthorizationContract } from '@/interfaces'; import { AuthorizationType } from '@/interfaces/generated'; import { PendingRequest, RequestBodyTypeEnum, RequestHeader } from '@/interfaces/http'; import { RouteDefinition } from '@/interfaces/routes'; @@ -80,7 +81,10 @@ describe('useRequestBuilderStore', () => { ...baseRoute, method: 'POST', schema: { - shape: {}, + shape: { + 'x-name': 'root', + 'x-required': false, + }, extractionErrors: null, }, }; @@ -120,7 +124,10 @@ describe('useRequestBuilderStore', () => { GET: { [RequestBodyTypeEnum.JSON]: '{}' }, }; const params = [{ key: 'page', value: '1' }]; - const auth = { type: AuthorizationType.Bearer, value: 'token' }; + const auth: AuthorizationContract = { + type: AuthorizationType.Bearer, + value: 'token', + }; store.updateRequestHeaders(headers); store.updateRequestBody(body); diff --git a/resources/js/tests/stores/useSettingsStore.test.ts b/resources/js/tests/stores/useSettingsStore.test.ts index b4ef20c..90dab86 100644 --- a/resources/js/tests/stores/useSettingsStore.test.ts +++ b/resources/js/tests/stores/useSettingsStore.test.ts @@ -35,7 +35,9 @@ describe('useSettingsStore', () => { await nextTick(); - expect(JSON.parse(window.localStorage.getItem(STORAGE_KEY)).theme).toBe('dark'); + expect(JSON.parse(window.localStorage?.getItem(STORAGE_KEY) ?? '{}').theme).toBe( + 'dark', + ); }); it('resets preferences to defaults', async () => { diff --git a/resources/js/tests/stores/useValueGeneratorStore.test.ts b/resources/js/tests/stores/useValueGeneratorStore.test.ts index 87d9ff7..a762f09 100644 --- a/resources/js/tests/stores/useValueGeneratorStore.test.ts +++ b/resources/js/tests/stores/useValueGeneratorStore.test.ts @@ -1,5 +1,6 @@ import { ValueGenerator } from '@/interfaces/ui'; import { useValueGeneratorStore } from '@/stores/generators/useValueGeneratorStore'; +import { Mock } from '@vitest/spy'; import { createPinia, setActivePinia } from 'pinia'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { computed, reactive, ref } from 'vue'; @@ -87,7 +88,7 @@ describe('useValueGeneratorStore', () => { commandStore.addToRecentGenerators.mockClear(); setSearchQuery.mockClear(); setSelectedCategory.mockClear(); - generators.forEach(generator => (generator.generate as vi.Mock).mockClear()); + generators.forEach(generator => (generator.generate as Mock).mockClear()); }); it('generates value and records generator usage', () => { diff --git a/resources/js/tests/utils/generateCurlCommand.test.ts b/resources/js/tests/utils/generateCurlCommand.test.ts index 4624011..d582bf0 100644 --- a/resources/js/tests/utils/generateCurlCommand.test.ts +++ b/resources/js/tests/utils/generateCurlCommand.test.ts @@ -60,10 +60,10 @@ describe('generateCurlCommand', () => { }); it('flags special authorization types', () => { - const request = { + const request: PendingRequest = { ...requestBase, authorization: { type: AuthorizationType.Impersonate, value: 1 }, - }; + } as PendingRequest; const { hasSpecialAuth } = generateCurlCommand( request,