From 620c41ceb4cae6d5141da05084a6b640f6686860 Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Fri, 21 Jul 2023 22:07:23 -0600 Subject: [PATCH] tests: Start writing tests, Splash and some of the `lib/` functions (#280) --- .github/workflows/test.yaml | 33 + .gitignore | 1 + package.json | 6 + .../texture-packs/TexturePacks.svelte | 2 +- src/components/games/setup/GameSetup.svelte | 2 +- src/lib/rpc/support.ts | 2 +- src/lib/utils/common.test.ts | 14 + src/lib/utils/{file.ts => file-dialogs.ts} | 0 src/lib/utils/github.test.ts | 284 + src/lib/utils/github.ts | 60 +- src/routes/settings/Folders.svelte | 2 +- src/splash/Splash.svelte | 37 +- src/splash/splash.test.ts | 198 + src/tests/setup.ts | 5 + tsconfig.json | 3 +- vite.config.ts | 2 +- vitest.config.ts | 20 + yarn.lock | 10960 +++++++++------- 18 files changed, 6655 insertions(+), 4976 deletions(-) create mode 100644 .github/workflows/test.yaml create mode 100644 src/lib/utils/common.test.ts rename src/lib/utils/{file.ts => file-dialogs.ts} (100%) create mode 100644 src/lib/utils/github.test.ts create mode 100644 src/splash/splash.test.ts create mode 100644 src/tests/setup.ts create mode 100644 vitest.config.ts diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..d7e9caf --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,33 @@ +name: 🧪 Tests + +on: + push: + branches: + - main + tags: + - v* + pull_request: + branches: + - main + +jobs: + frontend: + name: Frontend + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: yarn + + - name: Install NPM Dependencies + run: yarn install --frozen-lockfile + + - name: Run Tests + run: yarn test + + # TODO - capture and report on coverage diff --git a/.gitignore b/.gitignore index a97f5d5..704e309 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ TODO.md vite.config.ts.timestamp* .yarn/ .yarnrc.yml +coverage/ diff --git a/package.json b/package.json index 0620df5..14308c8 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,8 @@ "type": "module", "scripts": { "dev": "vite", + "test": "vitest", + "coverage": "vitest run --coverage", "build": "vite build", "preview": "vite preview", "tauri": "tauri", @@ -25,6 +27,8 @@ "@sveltejs/vite-plugin-svelte": "^2.4.2", "@tauri-apps/cli": "^1.4.0", "@tauri-apps/tauricon": "github:tauri-apps/tauricon", + "@testing-library/svelte": "^4.0.3", + "@vitest/coverage-v8": "^0.33.0", "@tsconfig/svelte": "^5.0.0", "ansi-to-span": "^0.0.1", "autoprefixer": "^10.4.14", @@ -33,6 +37,7 @@ "execa": "^7.1.1", "flowbite": "^1.6.5", "flowbite-svelte": "^0.39.2", + "jsdom": "^22.1.0", "postcss": "^8.4.26", "postcss-load-config": "^4.0.1", "prettier": "^2.8.8", @@ -42,6 +47,7 @@ "svelte-preprocess": "^5.0.3", "tailwindcss": "^3.3.3", "typescript": "^5.1.3", + "vitest": "^0.33.0", "vite": "^4.4.4" }, "dependencies": { diff --git a/src/components/games/features/texture-packs/TexturePacks.svelte b/src/components/games/features/texture-packs/TexturePacks.svelte index 2d8b501..3457dbf 100644 --- a/src/components/games/features/texture-packs/TexturePacks.svelte +++ b/src/components/games/features/texture-packs/TexturePacks.svelte @@ -18,7 +18,7 @@ extractNewTexturePack, listExtractedTexturePackInfo, } from "$lib/rpc/features"; - import { filePrompt } from "$lib/utils/file"; + import { filePrompt } from "$lib/utils/file-dialogs"; import Icon from "@iconify/svelte"; import { convertFileSrc } from "@tauri-apps/api/tauri"; import { diff --git a/src/components/games/setup/GameSetup.svelte b/src/components/games/setup/GameSetup.svelte index 5120eb8..da7dfa3 100644 --- a/src/components/games/setup/GameSetup.svelte +++ b/src/components/games/setup/GameSetup.svelte @@ -11,7 +11,7 @@ runCompiler, runDecompiler, } from "$lib/rpc/binaries"; - import { folderPrompt, isoPrompt } from "$lib/utils/file"; + import { folderPrompt, isoPrompt } from "$lib/utils/file-dialogs"; import { finalizeInstallation, isAVXRequirementMet, diff --git a/src/lib/rpc/support.ts b/src/lib/rpc/support.ts index 74334d4..89062a4 100644 --- a/src/lib/rpc/support.ts +++ b/src/lib/rpc/support.ts @@ -1,4 +1,4 @@ -import { saveFilePrompt } from "$lib/utils/file"; +import { saveFilePrompt } from "$lib/utils/file-dialogs"; import { invoke_rpc } from "./rpc"; export async function generateSupportPackage(): Promise { diff --git a/src/lib/utils/common.test.ts b/src/lib/utils/common.test.ts new file mode 100644 index 0000000..21787c5 --- /dev/null +++ b/src/lib/utils/common.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from "vitest"; +import { isInDebugMode } from "./common"; + +describe("isInDebugMode", () => { + it("should return true when in debug mode", async () => { + process.env["NODE_ENV"] = "development"; + expect(isInDebugMode()).toBeTruthy(); + }); + + it("should return true when in debug mode", async () => { + process.env["NODE_ENV"] = "not-development"; + expect(isInDebugMode()).toBeFalsy(); + }); +}); diff --git a/src/lib/utils/file.ts b/src/lib/utils/file-dialogs.ts similarity index 100% rename from src/lib/utils/file.ts rename to src/lib/utils/file-dialogs.ts diff --git a/src/lib/utils/github.test.ts b/src/lib/utils/github.test.ts new file mode 100644 index 0000000..ca93c16 --- /dev/null +++ b/src/lib/utils/github.test.ts @@ -0,0 +1,284 @@ +import { afterEach, describe, expect, it, vi, type Mock } from "vitest"; +import { arch, platform } from "@tauri-apps/api/os"; +import { listOfficialReleases } from "./github"; + +vi.mock("@tauri-apps/api/os"); +global.fetch = vi.fn(); + +function createFetchResponse(data: any) { + return { json: () => new Promise((resolve) => resolve(data)) }; +} + +function createFakeGithubReleaseAsset(assetName) { + return { + url: "https://api.github.com/repos/open-goal/jak-project/releases/assets/115111791", + id: 115111791, + node_id: "RA_kwDOEUK6OM4G3Hdv", + name: assetName, + label: "", + uploader: { + login: "github-actions[bot]", + id: 41898282, + node_id: "MDM6Qm90NDE4OTgyODI=", + avatar_url: "https://avatars.githubusercontent.com/in/15368?v=4", + gravatar_id: "", + url: "https://api.github.com/users/github-actions%5Bbot%5D", + html_url: "https://github.com/apps/github-actions", + followers_url: + "https://api.github.com/users/github-actions%5Bbot%5D/followers", + following_url: + "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + gists_url: + "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + starred_url: + "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + subscriptions_url: + "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + organizations_url: + "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + repos_url: "https://api.github.com/users/github-actions%5Bbot%5D/repos", + events_url: + "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + received_events_url: + "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + type: "Bot", + site_admin: false, + }, + content_type: "application/x-gtar", + state: "uploaded", + size: 21698082, + download_count: 399, + created_at: "2023-07-01T06:38:26Z", + updated_at: "2023-07-01T06:38:28Z", + browser_download_url: `https://github.com/open-goal/jak-project/releases/download/v0.1.38/${assetName}`, + }; +} + +function createFakeGithubRelease(assetNames: string[]) { + const assets = []; + for (const assetName of assetNames) { + assets.push(createFakeGithubReleaseAsset(assetName)); + } + return { + url: "https://api.github.com/repos/open-goal/jak-project/releases/110648537", + assets_url: + "https://api.github.com/repos/open-goal/jak-project/releases/110648537/assets", + upload_url: + "https://uploads.github.com/repos/open-goal/jak-project/releases/110648537/assets{?name,label}", + html_url: "https://github.com/open-goal/jak-project/releases/tag/v0.1.38", + id: 110648537, + author: { + login: "OpenGOALBot", + id: 99294829, + node_id: "U_kgDOBesebQ", + avatar_url: "https://avatars.githubusercontent.com/u/99294829?v=4", + gravatar_id: "", + url: "https://api.github.com/users/OpenGOALBot", + html_url: "https://github.com/OpenGOALBot", + followers_url: "https://api.github.com/users/OpenGOALBot/followers", + following_url: + "https://api.github.com/users/OpenGOALBot/following{/other_user}", + gists_url: "https://api.github.com/users/OpenGOALBot/gists{/gist_id}", + starred_url: + "https://api.github.com/users/OpenGOALBot/starred{/owner}{/repo}", + subscriptions_url: + "https://api.github.com/users/OpenGOALBot/subscriptions", + organizations_url: "https://api.github.com/users/OpenGOALBot/orgs", + repos_url: "https://api.github.com/users/OpenGOALBot/repos", + events_url: "https://api.github.com/users/OpenGOALBot/events{/privacy}", + received_events_url: + "https://api.github.com/users/OpenGOALBot/received_events", + type: "User", + site_admin: false, + }, + node_id: "RE_kwDOEUK6OM4GmFzZ", + tag_name: "v0.1.38", + target_commitish: "master", + name: "v0.1.38", + draft: false, + prerelease: false, + created_at: "2023-07-01T06:09:09Z", + published_at: "2023-07-01T06:38:29Z", + assets: assets, + tarball_url: + "https://api.github.com/repos/open-goal/jak-project/tarball/v0.1.38", + zipball_url: + "https://api.github.com/repos/open-goal/jak-project/zipball/v0.1.38", + body: "## What's Changed\n* ci: ensure linux runners have the proper OpenGL headers by @xTVaser in https://github.com/open-goal/jak-project/pull/2790\n\n\n**Full Changelog**: https://github.com/open-goal/jak-project/compare/v0.1.37...v0.1.38", + reactions: { + url: "https://api.github.com/repos/open-goal/jak-project/releases/110648537/reactions", + total_count: 4, + "+1": 0, + "-1": 0, + laugh: 0, + hooray: 3, + confused: 0, + heart: 0, + rocket: 0, + eyes: 1, + }, + mentions_count: 1, + }; +} + +describe("listOfficialReleases", () => { + afterEach(() => { + vi.clearAllMocks(); + vi.resetAllMocks(); + }); + + it("should retrieve intel macOS releases properly", async () => { + vi.mocked(platform).mockResolvedValue("darwin"); + vi.mocked(arch).mockResolvedValue("x86_64"); + (fetch as Mock).mockResolvedValue( + createFetchResponse([ + createFakeGithubRelease([ + "opengoal-macos-intel-v0.0.1.tar.gz", + "opengoal-windows-v0.0.1.zip", + "opengoal-linux-v0.0.1.tar.gz", + ]), + ]) + ); + const releases = await listOfficialReleases(); + expect(releases.length).toBe(1); + expect( + releases[0].downloadUrl.endsWith("opengoal-macos-intel-v0.0.1.tar.gz") + ).toBeTruthy(); + }); + + it("should not retrieve macOS ARM releases", async () => { + vi.mocked(platform).mockResolvedValue("darwin"); + vi.mocked(arch).mockResolvedValue("arm"); + (fetch as Mock).mockResolvedValue( + createFetchResponse([ + createFakeGithubRelease([ + "opengoal-macos-intel-v0.0.1.tar.gz", + "opengoal-windows-v0.0.1.zip", + "opengoal-linux-v0.0.1.tar.gz", + ]), + ]) + ); + const releases = await listOfficialReleases(); + expect(releases.length).toBe(1); + expect(releases[0].downloadUrl).toBeUndefined(); + }); + + it("should retrieve windows releases properly", async () => { + vi.mocked(platform).mockResolvedValue("win32"); + vi.mocked(arch).mockResolvedValue("x86_64"); + (fetch as Mock).mockResolvedValue( + createFetchResponse([ + createFakeGithubRelease([ + "opengoal-macos-intel-v0.0.1.tar.gz", + "opengoal-windows-v0.0.1.zip", + "opengoal-linux-v0.0.1.tar.gz", + ]), + ]) + ); + const releases = await listOfficialReleases(); + expect(releases.length).toBe(1); + expect( + releases[0].downloadUrl.endsWith("opengoal-windows-v0.0.1.zip") + ).toBeTruthy(); + }); + + it("should retrieve linux releases properly", async () => { + vi.mocked(platform).mockResolvedValue("linux"); + vi.mocked(arch).mockResolvedValue("x86_64"); + (fetch as Mock).mockResolvedValue( + createFetchResponse([ + createFakeGithubRelease([ + "opengoal-macos-intel-v0.0.1.tar.gz", + "opengoal-windows-v0.0.1.zip", + "opengoal-linux-v0.0.1.tar.gz", + ]), + ]) + ); + const releases = await listOfficialReleases(); + expect(releases.length).toBe(1); + expect( + releases[0].downloadUrl.endsWith("opengoal-linux-v0.0.1.tar.gz") + ).toBeTruthy(); + }); +}); + +describe("getLatestOfficialRelease", () => { + afterEach(() => { + vi.clearAllMocks(); + vi.resetAllMocks(); + }); + + it("should retrieve intel macOS releases properly", async () => { + vi.mocked(platform).mockResolvedValue("darwin"); + vi.mocked(arch).mockResolvedValue("x86_64"); + (fetch as Mock).mockResolvedValue( + createFetchResponse([ + createFakeGithubRelease([ + "opengoal-macos-intel-v0.0.1.tar.gz", + "opengoal-windows-v0.0.1.zip", + "opengoal-linux-v0.0.1.tar.gz", + ]), + ]) + ); + const releases = await listOfficialReleases(); + expect(releases.length).toBe(1); + expect( + releases[0].downloadUrl.endsWith("opengoal-macos-intel-v0.0.1.tar.gz") + ).toBeTruthy(); + }); + + it("should not retrieve macOS ARM releases", async () => { + vi.mocked(platform).mockResolvedValue("darwin"); + vi.mocked(arch).mockResolvedValue("arm"); + (fetch as Mock).mockResolvedValue( + createFetchResponse([ + createFakeGithubRelease([ + "opengoal-macos-intel-v0.0.1.tar.gz", + "opengoal-windows-v0.0.1.zip", + "opengoal-linux-v0.0.1.tar.gz", + ]), + ]) + ); + const releases = await listOfficialReleases(); + expect(releases.length).toBe(1); + expect(releases[0].downloadUrl).toBeUndefined(); + }); + + it("should retrieve windows releases properly", async () => { + vi.mocked(platform).mockResolvedValue("win32"); + vi.mocked(arch).mockResolvedValue("x86_64"); + (fetch as Mock).mockResolvedValue( + createFetchResponse([ + createFakeGithubRelease([ + "opengoal-macos-intel-v0.0.1.tar.gz", + "opengoal-windows-v0.0.1.zip", + "opengoal-linux-v0.0.1.tar.gz", + ]), + ]) + ); + const releases = await listOfficialReleases(); + expect(releases.length).toBe(1); + expect( + releases[0].downloadUrl.endsWith("opengoal-windows-v0.0.1.zip") + ).toBeTruthy(); + }); + + it("should retrieve linux releases properly", async () => { + vi.mocked(platform).mockResolvedValue("linux"); + vi.mocked(arch).mockResolvedValue("x86_64"); + (fetch as Mock).mockResolvedValue( + createFetchResponse([ + createFakeGithubRelease([ + "opengoal-macos-intel-v0.0.1.tar.gz", + "opengoal-windows-v0.0.1.zip", + "opengoal-linux-v0.0.1.tar.gz", + ]), + ]) + ); + const releases = await listOfficialReleases(); + expect(releases.length).toBe(1); + expect( + releases[0].downloadUrl.endsWith("opengoal-linux-v0.0.1.tar.gz") + ).toBeTruthy(); + }); +}); diff --git a/src/lib/utils/github.ts b/src/lib/utils/github.ts index 7a88c99..a796880 100644 --- a/src/lib/utils/github.ts +++ b/src/lib/utils/github.ts @@ -10,30 +10,54 @@ export interface ReleaseInfo { pendingAction: boolean; } +function isIntelMacOsRelease( + platform: string, + architecture: string, + assetName: string +): boolean { + return ( + platform === "darwin" && + architecture === "x86_64" && + assetName.startsWith("opengoal-macos-intel-v") + ); +} + +// TODO - go back and fix old asset names so windows/linux can be simplified +function isWindowsRelease( + platform: string, + architecture: string, + assetName: string +): boolean { + return ( + platform === "win32" && + (assetName.startsWith("opengoal-windows-v") || + (assetName.startsWith("opengoal-v") && assetName.includes("windows"))) + ); +} + +function isLinuxRelease( + platform: string, + architecture: string, + assetName: string +): boolean { + return ( + platform === "linux" && + (assetName.startsWith("opengoal-linux-v") || + (assetName.startsWith("opengoal-v") && assetName.includes("linux"))) + ); +} + async function getDownloadLinkForCurrentPlatform( - release + release: any ): Promise { const platformName = await platform(); const archName = await arch(); for (const asset of release.assets) { - if ( - platformName === "darwin" && - archName === "x86_64" && - asset.name.startsWith("opengoal-macos-intel-v") - // macOS doesn't have the old naming scheme - ) { + if (isIntelMacOsRelease(platformName, archName, asset.name)) { return asset.browser_download_url; - } else if ( - platformName === "win32" && - (asset.name.startsWith("opengoal-windows-v") || - (asset.name.startsWith("opengoal-v") && asset.name.includes("windows"))) - ) { + } else if (isWindowsRelease(platformName, archName, asset.name)) { return asset.browser_download_url; - } else if ( - platformName === "linux" && - (asset.name.startsWith("opengoal-linux-v") || - (asset.name.startsWith("opengoal-v") && asset.name.includes("linux"))) - ) { + } else if (isLinuxRelease(platformName, archName, asset.name)) { return asset.browser_download_url; } } @@ -44,7 +68,6 @@ export async function listOfficialReleases(): Promise { let releases = []; // TODO - handle rate limiting // TODO - long term - handle pagination (more than 100 releases) - // TODO - even longer term - extract this out into an API we control (avoid github rate limiting) -- will be needed for unofficial releases as well anyway const resp = await fetch( "https://api.github.com/repos/open-goal/jak-project/releases?per_page=100" ); @@ -68,7 +91,6 @@ export async function listOfficialReleases(): Promise { export async function getLatestOfficialRelease(): Promise { // TODO - handle rate limiting - // TODO - even longer term - extract this out into an API we control (avoid github rate limiting) -- will be needed for unofficial releases as well anyway const resp = await fetch( "https://api.github.com/repos/open-goal/jak-project/releases/latest" ); diff --git a/src/routes/settings/Folders.svelte b/src/routes/settings/Folders.svelte index fdef479..479e46a 100644 --- a/src/routes/settings/Folders.svelte +++ b/src/routes/settings/Folders.svelte @@ -4,7 +4,7 @@ setInstallationDirectory, } from "$lib/rpc/config"; import { VersionStore } from "$lib/stores/VersionStore"; - import { folderPrompt } from "$lib/utils/file"; + import { folderPrompt } from "$lib/utils/file-dialogs"; import { Label, Input } from "flowbite-svelte"; import { onMount } from "svelte"; import { _ } from "svelte-i18n"; diff --git a/src/splash/Splash.svelte b/src/splash/Splash.svelte index 46a23f6..ad6a5f9 100644 --- a/src/splash/Splash.svelte +++ b/src/splash/Splash.svelte @@ -2,7 +2,7 @@ import { openMainWindow } from "$lib/rpc/window"; import { onMount } from "svelte"; import logo from "$assets/images/icon.webp"; - import { folderPrompt } from "$lib/utils/file"; + import { folderPrompt } from "$lib/utils/file-dialogs"; import { deleteOldDataDirectory, getInstallationDirectory, @@ -12,10 +12,10 @@ setLocale, } from "$lib/rpc/config"; import { AVAILABLE_LOCALES } from "$lib/i18n/i18n"; - import { _ } from "svelte-i18n"; + import { locale as svelteLocale, _ } from "svelte-i18n"; let currentProgress = 10; - let currentStatusText = $_("splash_step_readingSettings"); + let currentStatusText = "Loading Locales..."; let selectLocale = false; let installationDirSet = true; @@ -26,6 +26,7 @@ onMount(async () => { // First, see if the user has selected a locale await checkLocale(); + currentStatusText = $_("splash_step_readingSettings"); }); // TODO - cleanup this code and make it easier to add steps like with @@ -36,6 +37,7 @@ if (locale === null) { // Prompt the user to select a locale selectLocale = true; + svelteLocale.set("en-US"); } else { // Set locale and continue setLocale(locale); @@ -74,25 +76,35 @@ currentStatusText = $_("splash_step_errorOpening"); } } + + async function handleLocaleChange(evt: Event) { + const selectElement = evt.target as HTMLSelectElement; + setLocale(selectElement.value); + selectLocale = false; + await checkDirectories(); + }
{#if selectLocale} {$_("splash_selectLocale")}