diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 7f32e64b..e5111869 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -5,27 +5,27 @@ module.exports = { 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:svelte/recommended', - 'prettier' + 'prettier', ], parser: '@typescript-eslint/parser', plugins: ['@typescript-eslint'], parserOptions: { sourceType: 'module', ecmaVersion: 2020, - extraFileExtensions: ['.svelte'] + extraFileExtensions: ['.svelte'], }, env: { browser: true, es2017: true, - node: true + node: true, }, overrides: [ { files: ['*.svelte'], parser: 'svelte-eslint-parser', parserOptions: { - parser: '@typescript-eslint/parser' - } - } - ] + parser: '@typescript-eslint/parser', + }, + }, + ], }; diff --git a/.prettierrc b/.prettierrc index 0580f3e9..b4cde23e 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,7 +1,7 @@ { "useTabs": false, "singleQuote": true, - "trailingComma": "none", + "trailingComma": "all", "printWidth": 100, "plugins": ["prettier-plugin-svelte"], "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] diff --git a/package-lock.json b/package-lock.json index 48816c1b..28c9e347 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "dotenv": "^16.3.1" }, "devDependencies": { - "@playwright/test": "^1.28.1", + "@playwright/test": "^1.42.0", "@skeletonlabs/skeleton": "2.6.0", "@skeletonlabs/tw-plugin": "0.3.0", "@sveltejs/adapter-auto": "^3.0.0", @@ -35,9 +35,10 @@ "prettier-plugin-svelte": "^3.1.2", "svelte": "^4.2.7", "svelte-check": "^3.6.0", + "svelte-eslint-parser": "^0.33.1", "tailwindcss": "3.3.6", "tslib": "^2.4.1", - "typescript": "^5.0.0", + "typescript": "^5.3.3", "vite": "^5.0.12", "vite-plugin-tailwind-purgecss": "0.2.0", "vitest": "^1.0.0" @@ -639,12 +640,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", - "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.0.tgz", + "integrity": "sha512-2k1HzC28Fs+HiwbJOQDUwrWMttqSLUVdjCqitBOjdCD0svWOMQUVqrXX6iFD7POps6xXAojsX/dGBpKnjZctLA==", "dev": true, "dependencies": { - "playwright": "1.40.1" + "playwright": "1.42.0" }, "bin": { "playwright": "cli.js" @@ -3496,12 +3497,12 @@ } }, "node_modules/playwright": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", - "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.0.tgz", + "integrity": "sha512-Ko7YRUgj5xBHbntrgt4EIw/nE//XBHOKVKnBjO1KuZkmkhlbgyggTe5s9hjqQ1LpN+Xg+kHsQyt5Pa0Bw5XpvQ==", "dev": true, "dependencies": { - "playwright-core": "1.40.1" + "playwright-core": "1.42.0" }, "bin": { "playwright": "cli.js" @@ -3514,9 +3515,9 @@ } }, "node_modules/playwright-core": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", - "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.0.tgz", + "integrity": "sha512-0HD9y8qEVlcbsAjdpBaFjmaTHf+1FeIddy8VJLeiqwhcNqGCBe4Wp2e8knpqiYbzxtxarxiXyNDw2cG8sCaNMQ==", "dev": true, "bin": { "playwright-core": "cli.js" diff --git a/package.json b/package.json index c28fa6d6..0ddef68c 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "playwright": "playwright test --ui" }, "devDependencies": { - "@playwright/test": "^1.28.1", + "@playwright/test": "^1.42.0", "@skeletonlabs/skeleton": "2.6.0", "@skeletonlabs/tw-plugin": "0.3.0", "@sveltejs/adapter-auto": "^3.0.0", @@ -37,9 +37,10 @@ "prettier-plugin-svelte": "^3.1.2", "svelte": "^4.2.7", "svelte-check": "^3.6.0", + "svelte-eslint-parser": "^0.33.1", "tailwindcss": "3.3.6", "tslib": "^2.4.1", - "typescript": "^5.0.0", + "typescript": "^5.3.3", "vite": "^5.0.12", "vite-plugin-tailwind-purgecss": "0.2.0", "vitest": "^1.0.0" diff --git a/pic-sure-theme.ts b/pic-sure-theme.ts index 621222b9..c32a3174 100644 --- a/pic-sure-theme.ts +++ b/pic-sure-theme.ts @@ -96,6 +96,6 @@ export const picSureTheme: CustomThemeConfig = { '--color-surface-600': '167 167 167', // #a7a7a7 '--color-surface-700': '140 140 140', // #8c8c8c '--color-surface-800': '112 112 112', // #707070 - '--color-surface-900': '91 91 91' // #5b5b5b - } + '--color-surface-900': '91 91 91', // #5b5b5b + }, }; diff --git a/playwright.config.ts b/playwright.config.ts index a458c6af..49812fed 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -3,11 +3,11 @@ import type { PlaywrightTestConfig } from '@playwright/test'; const config: PlaywrightTestConfig = { webServer: { command: 'npm run build && npm run preview', - port: 4173 + port: 4173, }, testDir: 'tests', testMatch: /(.+\.)?(test|spec)\.[jt]s/, - reporter: [['list'], ['html']] + reporter: [['list'], ['html']], }; export default config; diff --git a/postcss.config.cjs b/postcss.config.cjs index 5cbc2c7d..12a703d9 100644 --- a/postcss.config.cjs +++ b/postcss.config.cjs @@ -1,6 +1,6 @@ module.exports = { plugins: { tailwindcss: {}, - autoprefixer: {} - } + autoprefixer: {}, + }, }; diff --git a/src/lib/component/navigation.svelte b/src/lib/component/navigation.svelte index a6d8ae3e..80650dfe 100644 --- a/src/lib/component/navigation.svelte +++ b/src/lib/component/navigation.svelte @@ -4,14 +4,14 @@ import logo from '$lib/assets/app-logo.png'; const routes = [ - { path: '/', id: 'home', text: 'Home' }, { path: '/explorer', text: 'Query Builder' }, { path: '/users', text: 'Users' }, { path: '/api', text: 'API' }, { path: '/dataset', text: 'Dataset Management' }, { path: '/help', text: 'Help' }, { path: '/admin', text: 'Admin' }, - { path: '/#', id: 'logout', text: 'Logout' } + { path: '/admin/super', text: 'Super Admin' }, + { path: '/#', id: 'logout', text: 'Logout' }, ]; function getId({ path, id }: { path: string; id?: string; text: string }) { @@ -21,7 +21,9 @@ - + diff --git a/src/lib/configuration.ts b/src/lib/configuration.ts index 5f354cd7..db8dbdc8 100644 --- a/src/lib/configuration.ts +++ b/src/lib/configuration.ts @@ -1,4 +1,48 @@ export const branding = { + landing: { + searchPlaceholder: 'Search terms or variables of interest…', + description: + 'PIC-SURE can be used to search phenotypic variables and genomic variants, apply filters, build cohorts, and export participant-level data.', + actions: [ + { + description: 'Explore data, apply filters, and build cohorts', + icon: 'fa-solid fa-magnifying-glass fa-5x', + url: '/explorer', + }, + { + description: 'Manage Previously Saved Datasets', + icon: 'fa-solid fa-table-list fa-5x', + url: '/dataset', + }, + { + description: 'Take a tour of the PIC-SURE API', + icon: 'fa-solid fa-route fa-5x', + url: '/explorer?tour=true', + }, + { + description: 'Learn more about PIC‑SURE', + icon: 'fa-solid fa-circle-question fa-5x', + url: 'https://pic-sure.org/about', + }, + ], + stats: [ + { + title: 'Data Sources', + value: '10', + valueSrc: undefined, + }, + { + title: 'Variables', + value: '1,000,000', + valueSrc: undefined, + }, + { + title: 'Participants with Genomic Data', + value: '1,000', + valueSrc: undefined, + }, + ], + }, help: { links: [ { diff --git a/src/lib/models/value.ts b/src/lib/models/value.ts new file mode 100644 index 00000000..3a0ea9db --- /dev/null +++ b/src/lib/models/value.ts @@ -0,0 +1,5 @@ +export interface Value { + title: string; + value?: string; + valueSrc?: string; +} diff --git a/src/lib/navigation.svelte b/src/lib/navigation.svelte deleted file mode 100644 index a80d64a2..00000000 --- a/src/lib/navigation.svelte +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 309a0e67..f505af32 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,7 +1,175 @@ - -

Sample page data for the home page.

-
+
+
+
+ e.key === 'Enter' && search()} + required + /> + +
+

+ {branding.landing.description || + 'PIC-SURE can be used to search phenotypic variables and genomic variants, apply filters, build cohorts, and export participant-level data.'} +

+
+
+ {#each branding.landing.actions as link} + + +
{link.description}
+
+ {/each} +
+
+ {#await getValues()} + {#each branding.landing.stats as stat} +
+ +

{stat.title}

+
+ {/each} + {:then} + {#each values as stat} +
+
+ {stat.value} +
+

{stat.title}

+
+ {/each} + {:catch} + {#each branding.landing.stats as stat} +
+
N/A
+

{stat.title}

+
+ {/each} + {/await} +
+
+ + diff --git a/svelte.config.js b/svelte.config.js index cb51dadb..a76efce3 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -9,13 +9,13 @@ const config = { preprocess: [vitePreprocess()], vitePlugin: { - inspector: true + inspector: true, }, kit: { // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // If your environment is not supported or you settled on a specific environment, switch out the adapter. // See https://kit.svelte.dev/docs/adapters for more information about adapters. - adapter: adapter() - } + adapter: adapter(), + }, }; export default config; diff --git a/tailwind.config.ts b/tailwind.config.ts index 6a78f92e..608a53cc 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -9,18 +9,18 @@ export default { darkMode: 'class', content: [ './src/**/*.{html,js,svelte,ts}', - join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}') + join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}'), ], theme: { - extend: {} + extend: {}, }, plugins: [ forms, typography, skeleton({ themes: { - custom: [picSureTheme] - } - }) - ] + custom: [picSureTheme], + }, + }), + ], } satisfies Config; diff --git a/tests/lib/component/navigation/test.ts b/tests/lib/component/navigation/test.ts index 3d3156f1..6c3f82e0 100644 --- a/tests/lib/component/navigation/test.ts +++ b/tests/lib/component/navigation/test.ts @@ -6,7 +6,6 @@ import { datasets as mockData } from '../../../mock-data'; test.describe('navigation', () => { const routes = [ - { path: '/', id: 'nav-link-home', headerText: 'Home' }, { path: '/explorer', id: 'nav-link-explorer', headerText: 'Explorer/Query Builder' }, { path: '/users', id: 'nav-link-users', headerText: 'Users' }, { path: '/api', id: 'nav-link-api', headerText: 'API' }, @@ -43,6 +42,18 @@ test.describe('navigation', () => { await Promise.all(inactive); }); }); + test('Clicking the logo navigates to the landing page', async ({ page }) => { + // Given + await page.goto('/help'); + + // When + const logo = page.locator('#nav-logo'); + await logo.click(); + + // Then + await expect(page).toHaveURL('/'); + await expect(page.locator('#search-box')).toBeVisible(); + }); test('Print page hides navigation links', async ({ page }) => { // Given await page.goto('/'); diff --git a/tests/routes/landing/test.ts b/tests/routes/landing/test.ts new file mode 100644 index 00000000..fb95192d --- /dev/null +++ b/tests/routes/landing/test.ts @@ -0,0 +1,80 @@ +import { expect } from '@playwright/test'; +import { test } from '../../custom-context'; +import { branding } from '../../../src/lib/configuration'; + +test.describe('Landing page', () => { + test('Has expected search to go to explorer', async ({ page }) => { + // Given + await page.goto('/'); + // When + await page.fill('input', 'test'); + await page.keyboard.press('Enter'); + // Then + await expect(page).toHaveURL('/explorer?search=test'); + }); + test('Has expected search to go to explorer with spaces', async ({ page }) => { + // Given + await page.goto('/'); + // When + await page.fill('input', 'test with spaces'); + await page.keyboard.press('Enter'); + // Then + await expect(page).toHaveURL('/explorer?search=test%20with%20spaces'); + }); + branding.landing.stats.forEach(({ title, value, valueSrc }) => { + test(`Has expected stat of title: ${title}`, async ({ page }) => { + // Given + await page.goto('/'); + // Then + await expect(page.getByText(title, { exact: true })).toBeVisible(); + }); + test(`Has expected stat of value: ${value}`, async ({ page }) => { + // Given + await page.goto('/'); + // Then + //TODO: update to a better test when + if (!valueSrc) { + await expect(page.getByText(value, { exact: true })).toBeVisible(); + } + }); + }); + branding.landing.actions.forEach(({ description, icon, url }) => { + test(`Has expected action of description: ${description}`, async ({ page }) => { + // Given + await page.goto('/'); + // Then + await expect(page.getByText(description, { exact: true })).toBeVisible(); + }); + test(`Has expected icon of: ${icon}`, async ({ page }) => { + // Given + await page.goto('/'); + // Then + const iconElement = page.locator(`.${icon.replaceAll(' ', '.')}`); + const pattern = new RegExp(`^${icon}.*`); + await expect(iconElement).toBeVisible(); + await expect(iconElement).toHaveClass(pattern); + }); + test(`Card "${description}"'s click leads to ${url}`, async ({ page }) => { + // Given + await page.goto('/'); + + // When + const action = page.getByText(description, { exact: true }).locator('..'); + await action.isVisible(); + + // Then + if ((await action.getAttribute('target')) !== '_blank') { + await action.click(); + await expect(page).toHaveURL(`${url}`); + } else { + //check if new tab opened + const newTabPromise = page.waitForEvent('popup'); + await action.click(); + const newPage = await newTabPromise; + await newPage.waitForLoadState(); + await expect(newPage).not.toBeNull(); + await expect(newPage).toHaveURL(`${url}`); + } + }); + }); +}); diff --git a/tests/test.ts b/tests/test.ts deleted file mode 100644 index 47dc5729..00000000 --- a/tests/test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { expect, test } from '@playwright/test'; - -const routes = [ - { path: '/', id: 'nav-link-home', headerText: 'Home' }, - { path: '/explorer', id: 'nav-link-explorer', headerText: 'Explorer/Query Builder' }, - { path: '/users', id: 'nav-link-users', headerText: 'Users' }, - { path: '/api', id: 'nav-link-api', headerText: 'API' }, - { path: '/dataset', id: 'nav-link-dataset', headerText: 'Dataset Management' }, - { path: '/help', id: 'nav-link-help', headerText: 'Help' }, - { path: '/admin', id: 'nav-link-admin', headerText: 'Admin' }, - { path: '/admin/super', id: 'nav-link-admin-super', headerText: 'Super Admin' } -]; - -test.describe('Navigation', () => { - routes.forEach((route) => { - test(`${route.path} page has expected heading`, async ({ page }) => { - await page.goto('/'); - const navItem = page.locator('#' + route.id); - await navItem.click(); - await expect(page.locator('.main-content>h1')).toHaveText(route.headerText); - }); - test(`${route.path} navigation bar has correct active element`, async ({ page }) => { - await page.goto(route.path); - - // Check that this element is active - const navItem = page.locator('#' + route.id); - await expect(navItem).toHaveAttribute('aria-current', 'page'); - - // Check that other elements aren't active - const inactive = routes - .filter((altRoute) => altRoute.path !== route.path) - .map((altRoute) => { - const navItem = page.locator('#' + altRoute.id); - return expect(navItem).not.toHaveAttribute('aria-current'); - }); - await Promise.all(inactive); - }); - }); -}); diff --git a/vite.config.ts b/vite.config.ts index 79292926..029003f0 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,5 +3,5 @@ import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; export default defineConfig({ - plugins: [sveltekit(), purgeCss()] + plugins: [sveltekit(), purgeCss()], });