From b69b24ba7e0553a6d6204ab43d674e008b19dea9 Mon Sep 17 00:00:00 2001 From: Xavier FACQ Date: Tue, 30 Apr 2024 08:01:37 +0200 Subject: [PATCH] Fetch constants from api instead of having values hardcoded (#2787) * Fetch constants from api instead of having values hardcoded * Prepare code for final version * Add dedicated tests * Correct tests * Correct two words path by using an underscore * Ajust final paths for new types (Architectures & OperatingSystems) --------- Co-authored-by: Martijn Verburg --- src/__fixtures__/hooks.tsx | 11 ++- .../__tests__/DownloadDropdowns.test.tsx | 27 ++++--- .../DownloadDropdowns.test.tsx.snap | 56 +++++++++++-- src/components/DownloadDropdowns/index.tsx | 15 ++-- .../fetchConstants.test.tsx.snap | 37 +++++++++ src/hooks/__tests__/fetchConstants.test.tsx | 79 +++++++++++++++++++ src/hooks/fetchConstants.tsx | 69 ++++++++++++++++ src/hooks/index.tsx | 1 + .../__snapshots__/marketplace.test.tsx.snap | 69 +++------------- src/pages/__tests__/marketplace.test.tsx | 12 +++ .../__snapshots__/releases.test.tsx.snap | 69 +++------------- src/pages/temurin/__tests__/releases.test.tsx | 12 +++ src/util/__tests__/defaults.test.tsx | 4 +- src/util/defaults.tsx | 2 - 14 files changed, 319 insertions(+), 144 deletions(-) create mode 100644 src/hooks/__tests__/__snapshots__/fetchConstants.test.tsx.snap create mode 100644 src/hooks/__tests__/fetchConstants.test.tsx create mode 100644 src/hooks/fetchConstants.tsx diff --git a/src/__fixtures__/hooks.tsx b/src/__fixtures__/hooks.tsx index 1902062dd..590cba012 100644 --- a/src/__fixtures__/hooks.tsx +++ b/src/__fixtures__/hooks.tsx @@ -12,6 +12,8 @@ import { NewsResponse, ReleaseAsset, TemurinReleases, + OperatingSystem, + Architecture } from '../hooks'; export const createRandomTemurinRelease = (installer): ReleaseAsset => ({ @@ -232,7 +234,6 @@ export const createMockReleaseNotesAPI = (number): ReleaseNoteAPIResponse => ({ })) }); - export const createMockTemurinFeatureReleaseAPI = (installer): MockTemurinFeatureReleaseAPI => ({ id: 'id_mock', download_count: 0, @@ -377,3 +378,11 @@ export const createMockAdoptiumContributorsApi = (): ContributorApiResponse => ( site_admin: false } ); + +export const mockOsesAPI = (): OperatingSystem[] => ( + [{name: "mock_macos"}, {name: "mock_linux"}, {name: "mock_windows"}] +); + +export const mockArchesAPI = (): Architecture[] => ( + [{name: "mock_aarch64"}, {name: "mock_ppc64"}, {name: "mock_x64"} +]); diff --git a/src/components/DownloadDropdowns/__tests__/DownloadDropdowns.test.tsx b/src/components/DownloadDropdowns/__tests__/DownloadDropdowns.test.tsx index 003ea3d15..95bd3ed1c 100644 --- a/src/components/DownloadDropdowns/__tests__/DownloadDropdowns.test.tsx +++ b/src/components/DownloadDropdowns/__tests__/DownloadDropdowns.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { act, render, fireEvent, waitFor } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest' -import { createRandomTemurinReleases } from '../../../__fixtures__/hooks'; +import { createRandomTemurinReleases, mockOsesAPI, mockArchesAPI } from '../../../__fixtures__/hooks'; import DownloadDropdowns from '..'; import queryString from 'query-string'; @@ -11,6 +11,17 @@ const Table = () => { ); }; +vi.mock('../../../hooks/fetchConstants', () => { + return { + fetchOses: () => { + return mockOsesAPI(); + }, + fetchArches: () => { + return mockArchesAPI(); + } + }; +}); + vi.mock('../../VendorSelector', () => { return { default: () =>
vendor-selector
, @@ -19,8 +30,6 @@ vi.mock('../../VendorSelector', () => { vi.mock('../../../util/defaults', () => { return { - oses: ['mock_os'], - arches: ['mock_arch'], packageTypes: ['mock_pkg'], defaultPackageType: 'mock_pkg', defaultArchitecture: 'mock_arch', @@ -71,14 +80,14 @@ describe('DownloadDropdowns component', () => { // Simulate a user using dropdowns select = getByTestId('os-filter'); await act(async () => { - fireEvent.change(select, { target: { value: 'mock_os' } }); + fireEvent.change(select, { target: { value: 'mock_linux' } }); }); expect(updater).toHaveBeenCalledTimes(2); select = getByTestId('arch-filter'); await act(async () => { - fireEvent.change(select, { target: { value: 'mock_arch' } }); + fireEvent.change(select, { target: { value: 'mock_x64' } }); }); expect(updater).toHaveBeenCalledTimes(3); @@ -100,7 +109,7 @@ describe('DownloadDropdowns component', () => { }); it('renders correctly - use URL param os=mock_os', async () => { - queryString.parse = vi.fn().mockReturnValue({'os': "mock_os"}); + queryString.parse = vi.fn().mockReturnValue({'os': "mock_linux"}); let getByRole; await act(async () => { @@ -113,11 +122,11 @@ describe('DownloadDropdowns component', () => { )); }); - expect(getByRole('option', { name: 'Mock_os' }).selected).toBeTruthy() + expect(getByRole('option', { name: 'Mock_linux' }).selected).toBeTruthy() }); it('renders correctly - use URL param arch=mock_arch', async () => { - queryString.parse = vi.fn().mockReturnValue({'arch': "mock_arch"}); + queryString.parse = vi.fn().mockReturnValue({'arch': "mock_x64"}); let getByRole; await act(async () => { @@ -130,7 +139,7 @@ describe('DownloadDropdowns component', () => { )); }); - expect(getByRole('option', { name: 'mock_arch' }).selected).toBeTruthy() + expect(getByRole('option', { name: 'mock_x64' }).selected).toBeTruthy() }); it('renders correctly - use URL param package=mock_pkg', async () => { diff --git a/src/components/DownloadDropdowns/__tests__/__snapshots__/DownloadDropdowns.test.tsx.snap b/src/components/DownloadDropdowns/__tests__/__snapshots__/DownloadDropdowns.test.tsx.snap index c958285c8..78206fdc5 100644 --- a/src/components/DownloadDropdowns/__tests__/__snapshots__/DownloadDropdowns.test.tsx.snap +++ b/src/components/DownloadDropdowns/__tests__/__snapshots__/DownloadDropdowns.test.tsx.snap @@ -29,9 +29,19 @@ exports[`DownloadDropdowns component > renders correctly - marketplace 1`] = ` Text + + @@ -56,9 +66,19 @@ exports[`DownloadDropdowns component > renders correctly - marketplace 1`] = ` Text + + @@ -144,9 +164,19 @@ exports[`DownloadDropdowns component > renders correctly 1`] = ` Text + + @@ -171,9 +201,19 @@ exports[`DownloadDropdowns component > renders correctly 1`] = ` Text + + diff --git a/src/components/DownloadDropdowns/index.tsx b/src/components/DownloadDropdowns/index.tsx index 8cd95d0a2..f2e3cf996 100644 --- a/src/components/DownloadDropdowns/index.tsx +++ b/src/components/DownloadDropdowns/index.tsx @@ -7,7 +7,8 @@ import VendorSelector from '../VendorSelector' import { detectOS, UserOS } from '../../util/detectOS'; import { setURLParam } from '../../util/setURLParam'; import { capitalize } from '../../util/capitalize'; -import { oses, arches, packageTypes, defaultArchitecture, defaultPackageType} from '../../util/defaults'; +import { packageTypes, defaultArchitecture, defaultPackageType} from '../../util/defaults'; +import { fetchOses, fetchArches} from '../../hooks/fetchConstants'; const DownloadDropdowns = ({updaterAction, marketplace, Table}) => { @@ -42,7 +43,7 @@ const DownloadDropdowns = ({updaterAction, marketplace, Table}) => { const osParam = queryStringParams.os; if (osParam) { let sop = osParam.toString().toLowerCase(); - if(oses.findIndex(os => os.toLowerCase() === sop) >= 0) + if(fetchOses(true).findIndex(os => os.name.toLowerCase() === sop) >= 0) defaultSelectedOS = sop; } @@ -51,7 +52,7 @@ const DownloadDropdowns = ({updaterAction, marketplace, Table}) => { const archParam = queryStringParams.arch; if (archParam) { let sap = archParam.toString().toLowerCase(); - if(arches.findIndex(a => a.toLowerCase() === sap) >= 0) + if(fetchArches(true).findIndex(a => a.name.toLowerCase() === sap) >= 0) defaultSelectedArch = sap; } @@ -182,9 +183,9 @@ const DownloadDropdowns = ({updaterAction, marketplace, Table}) => { @@ -193,9 +194,9 @@ const DownloadDropdowns = ({updaterAction, marketplace, Table}) => { diff --git a/src/hooks/__tests__/__snapshots__/fetchConstants.test.tsx.snap b/src/hooks/__tests__/__snapshots__/fetchConstants.test.tsx.snap new file mode 100644 index 000000000..072a992e8 --- /dev/null +++ b/src/hooks/__tests__/__snapshots__/fetchConstants.test.tsx.snap @@ -0,0 +1,37 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`fetchArches > URL is set correctly 1`] = ` +[ + { + "name": [ + { + "name": "mock_aarch64", + }, + { + "name": "mock_ppc64", + }, + { + "name": "mock_x64", + }, + ], + }, +] +`; + +exports[`fetchOses > URL is set correctly 1`] = ` +[ + { + "name": [ + { + "name": "mock_macos", + }, + { + "name": "mock_linux", + }, + { + "name": "mock_windows", + }, + ], + }, +] +`; diff --git a/src/hooks/__tests__/fetchConstants.test.tsx b/src/hooks/__tests__/fetchConstants.test.tsx new file mode 100644 index 000000000..393f45465 --- /dev/null +++ b/src/hooks/__tests__/fetchConstants.test.tsx @@ -0,0 +1,79 @@ +import { renderHook, waitFor } from '@testing-library/react' +import { describe, expect, it, vi } from 'vitest' +import { fetchArches, fetchOses } from '../fetchConstants'; +import { mockArchesAPI, mockOsesAPI } from '../../__fixtures__/hooks'; +import axios from 'axios' +import MockAdapter from 'axios-mock-adapter'; + +const mock = new MockAdapter(axios); + +afterEach(() => { + vi.clearAllMocks(); + mock.reset(); +}); + +afterAll(() => { + mock.restore(); +}); + +describe('fetchArches', () => { + it('URL is set correctly', async () => { + const mockResponse = [mockArchesAPI()]; + + mock.onGet().reply(200, mockResponse); + let spy = vi.spyOn(axios, "get"); + + const { result } = renderHook(() => fetchArches(true)); + + await waitFor(() => { + expect(result.current.length).toBeGreaterThan(0) + }, { interval: 1 }); + expect(spy).toHaveBeenCalledTimes(1) + expect(spy).toHaveBeenCalledWith( + "https://api.adoptium.net/v3/types/architectures" + ); + expect(result.current).toMatchSnapshot() + }) + + it('Result is empty on error', async() => { + mock.onGet().reply(500); + let spy = vi.spyOn(axios, "get"); + + const { result } = renderHook(() => fetchArches(true)); + await waitFor(() => { + expect(result.current.length).toBe(0) + }, { interval: 1 }); + expect(spy).toHaveBeenCalledTimes(1) + }) +}); + +describe('fetchOses', () => { + it('URL is set correctly', async () => { + const mockResponse = [mockOsesAPI()]; + + mock.onGet().reply(200, mockResponse); + let spy = vi.spyOn(axios, "get"); + + const { result } = renderHook(() => fetchOses(true)); + + await waitFor(() => { + expect(result.current.length).toBeGreaterThan(0) + }, { interval: 1 }); + expect(spy).toHaveBeenCalledTimes(1) + expect(spy).toHaveBeenCalledWith( + "https://api.adoptium.net/v3/types/operating_systems" + ); + expect(result.current).toMatchSnapshot() + }) + + it('Result is empty on error', async() => { + mock.onGet().reply(500); + let spy = vi.spyOn(axios, "get"); + + const { result } = renderHook(() => fetchOses(true)); + await waitFor(() => { + expect(result.current.length).toBe(0) + }, { interval: 1 }); + expect(spy).toHaveBeenCalledTimes(1) + }) +}); \ No newline at end of file diff --git a/src/hooks/fetchConstants.tsx b/src/hooks/fetchConstants.tsx new file mode 100644 index 000000000..f8f0353cf --- /dev/null +++ b/src/hooks/fetchConstants.tsx @@ -0,0 +1,69 @@ +import { useEffect, useState } from 'react'; +import axios from 'axios'; + +const baseUrl = 'https://api.adoptium.net/v3'; + +export function fetchOses(isVisible: boolean): OperatingSystem[] { + + const [oses, setOses] = useState([]); + + useEffect(() => { + if (isVisible) { + (async () => { + const url = `${baseUrl}/types/operating_systems`; + + axios.get(url) + .then(function (response) { + const newOses = response.data.map(s => { + const o: OperatingSystem = { name: s } + return o + }) + + setOses(newOses); + }) + .catch(function (error) { + setOses([]); + }); + })(); + } + }, [isVisible]); + + return oses; +} + +export function fetchArches(isVisible: boolean): Architecture[] { + + const [arches, setArches] = useState([]); + + useEffect(() => { + if (isVisible) { + (async () => { + const url = `${baseUrl}/types/architectures`; + + axios.get(url) + .then(function (response) { + const newArches = response.data.map(s => { + const a: Architecture = { name: s } + if(a.name === 'x32') a.name = 'x86' + return a; + }) + + setArches(newArches); + }) + .catch(function (error) { + setArches([]); + }); + })(); + } + }, [isVisible]); + + return arches; +} + +export interface OperatingSystem { + name: string; +} + +export interface Architecture { + name: string; +} diff --git a/src/hooks/index.tsx b/src/hooks/index.tsx index f60a63beb..8254e51af 100644 --- a/src/hooks/index.tsx +++ b/src/hooks/index.tsx @@ -6,6 +6,7 @@ export * from './fetchTemurinArchive'; export * from './fetchLatestTemurin'; export * from './fetchNews'; export * from './fetchReleaseNotes'; +export * from './fetchConstants'; export interface VersionMetaData { major: number; diff --git a/src/pages/__tests__/__snapshots__/marketplace.test.tsx.snap b/src/pages/__tests__/__snapshots__/marketplace.test.tsx.snap index 72f76a027..61f1f6b25 100644 --- a/src/pages/__tests__/__snapshots__/marketplace.test.tsx.snap +++ b/src/pages/__tests__/__snapshots__/marketplace.test.tsx.snap @@ -97,34 +97,19 @@ exports[`Marketplace page > renders correctly 1`] = ` Text - - - @@ -149,49 +134,19 @@ exports[`Marketplace page > renders correctly 1`] = ` Text - - - - - - diff --git a/src/pages/__tests__/marketplace.test.tsx b/src/pages/__tests__/marketplace.test.tsx index 659fbc3a1..00ee860d4 100644 --- a/src/pages/__tests__/marketplace.test.tsx +++ b/src/pages/__tests__/marketplace.test.tsx @@ -5,9 +5,21 @@ import { axe } from 'vitest-axe'; import Marketplace, { Head } from '../marketplace'; import AxiosInstance from 'axios' import MockAdapter from 'axios-mock-adapter'; +import { mockOsesAPI, mockArchesAPI } from '../../__fixtures__/hooks'; const mock = new MockAdapter(AxiosInstance); +vi.mock('../../hooks/fetchConstants', () => { + return { + fetchOses: () => { + return mockOsesAPI(); + }, + fetchArches: () => { + return mockArchesAPI(); + } + }; +}); + vi.mock('../../util/shuffle', () => { return { shuffle: (array) => { diff --git a/src/pages/temurin/__tests__/__snapshots__/releases.test.tsx.snap b/src/pages/temurin/__tests__/__snapshots__/releases.test.tsx.snap index abac3c2ee..45c2a33fa 100644 --- a/src/pages/temurin/__tests__/__snapshots__/releases.test.tsx.snap +++ b/src/pages/temurin/__tests__/__snapshots__/releases.test.tsx.snap @@ -74,34 +74,19 @@ exports[`Releases page > renders correctly 1`] = ` Text - - - @@ -126,49 +111,19 @@ exports[`Releases page > renders correctly 1`] = ` Text - - - - - - diff --git a/src/pages/temurin/__tests__/releases.test.tsx b/src/pages/temurin/__tests__/releases.test.tsx index 3ce8f52b3..7f652bd3d 100644 --- a/src/pages/temurin/__tests__/releases.test.tsx +++ b/src/pages/temurin/__tests__/releases.test.tsx @@ -5,9 +5,21 @@ import { axe } from 'vitest-axe'; import Releases, { Head } from '../releases'; import AxiosInstance from 'axios' import MockAdapter from 'axios-mock-adapter'; +import { mockOsesAPI, mockArchesAPI } from '../../../__fixtures__/hooks'; const mock = new MockAdapter(AxiosInstance); +vi.mock('../../../hooks/fetchConstants', () => { + return { + fetchOses: () => { + return mockOsesAPI(); + }, + fetchArches: () => { + return mockArchesAPI(); + } + }; +}); + afterEach(() => { vi.clearAllMocks(); }); diff --git a/src/util/__tests__/defaults.test.tsx b/src/util/__tests__/defaults.test.tsx index 765a6ec07..cb2771b35 100644 --- a/src/util/__tests__/defaults.test.tsx +++ b/src/util/__tests__/defaults.test.tsx @@ -1,10 +1,8 @@ -import { arches, defaultArchitecture, defaultPackageType, oses, packageTypes } from "../defaults"; +import { defaultArchitecture, defaultPackageType, packageTypes } from "../defaults"; import { describe, expect, it } from 'vitest' describe("defaults", () => { it("check types", () => { - expect(oses).toBeInstanceOf(Object); - expect(arches).toBeInstanceOf(Object); expect(packageTypes).toBeInstanceOf(Object); expect(typeof defaultPackageType).toBe("string"); expect(typeof defaultArchitecture).toBe("string"); diff --git a/src/util/defaults.tsx b/src/util/defaults.tsx index a61741749..f82a9788d 100644 --- a/src/util/defaults.tsx +++ b/src/util/defaults.tsx @@ -1,7 +1,5 @@ // This file is used to store defaults which can be accessed by other Javascript/Typescript functions -export const oses = ['Linux', 'alpine-linux', 'Windows', 'mac', 'AIX', 'Solaris']; -export const arches = ['x64', 'x86', 'aarch64', 's390x', 'ppc64le', 'ppc64', 'arm', 'sparcv9' ,'riscv64']; export const packageTypes = ['JDK', 'JRE']; let defaultPackageType = 'jdk';