Merge pull request #47 from zschaffer/dev
merge: updated readme and filter functions
This commit is contained in:
commit
51a63811d0
|
@ -1,3 +1,4 @@
|
|||
coverage
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
|
@ -7,3 +8,4 @@ node_modules
|
|||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
/coverage
|
24
README.md
24
README.md
|
@ -3,5 +3,27 @@
|
|||
## About
|
||||
|
||||
This project is a collaborative effort to catalogue and rate everything in existence.
|
||||
Written in `Svelte` with `TailwindCSS` and `Sanity`.
|
||||
Written in `Svelte` with `TailwindCSS`, `Typescript`, and `Sanity`. Testing is implemented in `Jest` and `Playwright`.
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
npm run dev
|
||||
```
|
||||
Runs client in `vite`.
|
||||
|
||||
```bash
|
||||
yarn test:unit
|
||||
yarn test
|
||||
```
|
||||
Testing: Unit tests run in `Jest`, integration tests run in `Playwright`.
|
||||
|
||||
## Functionality
|
||||
|
||||
Home Page and Product View
|
||||
![home](static/home.gif)
|
||||
Live Product Searching
|
||||
![search](static/searchRecord.gif)
|
||||
Filtering/Sorting
|
||||
![filter/sort](static/filterSortRecord.gif)
|
||||
|
||||
|
|
20
package.json
20
package.json
|
@ -11,30 +11,44 @@
|
|||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --check --plugin-search-dir=. . && eslint .",
|
||||
"format": "prettier --write --plugin-search-dir=. ."
|
||||
"format": "prettier --write --plugin-search-dir=. .",
|
||||
"test:unit": "vitest",
|
||||
"coverage": "vitest run --coverage"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.18.6",
|
||||
"@babel/preset-env": "^7.18.9",
|
||||
"@playwright/test": "^1.22.2",
|
||||
"@sveltejs/adapter-auto": "next",
|
||||
"@sveltejs/kit": "next",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/svelte": "^3.1.3",
|
||||
"@testing-library/user-event": "^14.3.0",
|
||||
"@types/testing-library__jest-dom": "^5.14.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
||||
"@typescript-eslint/parser": "^5.27.0",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"babel-jest": "^28.1.3",
|
||||
"c8": "^7.12.0",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"cz-conventional-changelog": "3.3.0",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"jest-environment-jsdom": "^28.1.3",
|
||||
"jsdom": "^20.0.0",
|
||||
"msw": "^0.44.2",
|
||||
"postcss": "^8.4.14",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-plugin-svelte": "^2.7.0",
|
||||
"svelte": "^3.44.0",
|
||||
"svelte-check": "^2.7.1",
|
||||
"svelte-jester": "^2.3.2",
|
||||
"svelte-preprocess": "^4.10.7",
|
||||
"tailwindcss": "^3.1.5",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.7.2",
|
||||
"vite": "^3"
|
||||
"vite": "^3",
|
||||
"vitest": "^0.18.1"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
|
|
@ -4,7 +4,8 @@ const config: PlaywrightTestConfig = {
|
|||
webServer: {
|
||||
command: 'npm run build && npm run preview',
|
||||
port: 3000
|
||||
}
|
||||
},
|
||||
testMatch: 'tests/**/*.ts'
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import matchers from '@testing-library/jest-dom/matchers';
|
||||
import { expect } from 'vitest';
|
||||
|
||||
expect.extend(matchers);
|
|
@ -0,0 +1,16 @@
|
|||
import { getAvgRating } from './'
|
||||
|
||||
export const defaultFilter = { selectedCat: 0, selectedRating: 0 }
|
||||
|
||||
|
||||
export const filterByCat = ($products, value, $tags) =>
|
||||
$products.filter((product) => {
|
||||
if (product.tags) {
|
||||
const selectedTagId = $tags.find((tag) => tag.name === value)._id;
|
||||
const productTags = product.tags.map((tag) => tag._ref);
|
||||
if (productTags.includes(selectedTagId)) return true;
|
||||
} else return false;
|
||||
});
|
||||
|
||||
export const filterByRating = (products, value) => products.filter((product) => product.rating && getAvgRating(product.rating) >= Number(value));
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* @returns {number} Average rating
|
||||
*/
|
||||
export const getAvgRating = (ratings) => {
|
||||
if (!ratings) return 0;
|
||||
if (!ratings) return 0;
|
||||
if (ratings.length === 1) return ratings[0].rating;
|
||||
else return ratings.reduce((prev, curr) => prev.rating + curr.rating) / ratings.length;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import { getAvgRating } from '$helpers/getAvgRating.js';
|
||||
|
||||
let testRating1 = {
|
||||
_key: '5a80f4748d91',
|
||||
comments: 'The new one seems pretty good but my old one died and got sticky.',
|
||||
emotion: { _ref: '633f8d5f-897a-461f-a817-ee910f6ad614', _type: 'reference' },
|
||||
name: 'xyn',
|
||||
rating: 4
|
||||
};
|
||||
|
||||
let testRating2 = {
|
||||
_key: '5a80f4748d91',
|
||||
comments: 'This is a fake comment for testing, wahoo',
|
||||
emotion: { _ref: '633f8d5f-897a-461f-a817-ee910f6ad614', _type: 'reference' },
|
||||
name: 'zane',
|
||||
rating: 3
|
||||
};
|
||||
|
||||
test('return false if no parameters are passed', async () => {
|
||||
expect(getAvgRating()).toBeFalsy();
|
||||
});
|
||||
|
||||
test('return false if array with empty object is passed', async () => {
|
||||
expect(getAvgRating([{}])).toBeFalsy();
|
||||
});
|
||||
|
||||
test('return single rating if only one rating is passed', async () => {
|
||||
expect(getAvgRating([testRating1])).toBe(testRating1.rating);
|
||||
});
|
||||
|
||||
test('return average of 2 ratings passed', async () => {
|
||||
expect(getAvgRating([testRating1, testRating2])).toBe(
|
||||
(testRating1.rating + testRating2.rating) / 2
|
||||
);
|
||||
});
|
|
@ -1,4 +1,5 @@
|
|||
export * from './toProduct';
|
||||
export * from './parse';
|
||||
export * from './normalize';
|
||||
export * from './getAvgRating';
|
||||
export * from './getAvgRating';
|
||||
export * from './param';
|
||||
export * from './filters'
|
|
@ -1,6 +0,0 @@
|
|||
/**
|
||||
* @param {string} str Any string
|
||||
* @returns {string} Lowercased string without any extra spaces
|
||||
*/
|
||||
export const normalize = (str) => str.toLowerCase().trim()
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { normalize, parseSlug, parseName } from './';
|
||||
|
||||
/**
|
||||
* Gets parameter from URL of field. Defaults to 'product'
|
||||
* @param {string} field='product'
|
||||
* @returns {string} parameter of field
|
||||
*/
|
||||
export const getProdParam = (field = 'product') =>
|
||||
new URLSearchParams(window.location.search).get(field) || ''
|
||||
|
||||
/**
|
||||
* Finds a product in products store with matching name as parameter.
|
||||
* @param {*} param parameter
|
||||
* @param {*} $products products store
|
||||
* @returns
|
||||
*/
|
||||
export const findProdFromParam = (param, $products) =>
|
||||
$products.find(({ name }) => normalize(name) === parseSlug(param)) || {}
|
||||
|
||||
/**
|
||||
* Resets params and navigates back to index
|
||||
*/
|
||||
export const resetParams = () => history.pushState({}, '', new URL(window.location.origin));
|
||||
|
||||
/**
|
||||
* Updates a url with search parameters with name of product parameter.
|
||||
* @param {URL} url a URL object
|
||||
* @param {object} product
|
||||
* @returns {URL} a URL object with search parameters appended.
|
||||
*/
|
||||
export const setUrlParam = (url, product) => {
|
||||
url.searchParams.set('product', parseName(product.name));
|
||||
return url;
|
||||
};
|
||||
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
import {normalize }from './normalize';
|
||||
|
||||
/**
|
||||
* @param {string} str Any string
|
||||
* @returns {string} Lowercased string without any extra spaces
|
||||
*/
|
||||
export const normalize = (str) => str.toLowerCase().trim()
|
||||
|
||||
/**
|
||||
* @param {string} slug Slug of a product name
|
||||
|
|
|
@ -1,15 +1,4 @@
|
|||
import { parseName, parseSlug, normalize } from './';
|
||||
|
||||
/**
|
||||
* Updates a url with search parameters to match product name of product passed in.
|
||||
* @param {URL} url a URL object
|
||||
* @param {object} product
|
||||
* @returns {URL} a URL object with search parameters appended.
|
||||
*/
|
||||
const setUrlParam = (url, product) => {
|
||||
url.searchParams.set('product', parseName(product.name));
|
||||
return url;
|
||||
};
|
||||
import { parseSlug, normalize, setUrlParam, resetParams } from './';
|
||||
|
||||
/**
|
||||
* Updates Window URL and checks that it is equal to product name
|
||||
|
@ -31,5 +20,5 @@ const didWinUrlUpdate = (url, product) => {
|
|||
* @param {store} currentProduct currentProduct store
|
||||
*/
|
||||
export const toProduct = (product, currentProduct) =>
|
||||
didWinUrlUpdate(setUrlParam(new URL(window.location), product), product) &&
|
||||
currentProduct.set(product)
|
||||
didWinUrlUpdate(setUrlParam(new URL(window.location), product), product)?
|
||||
currentProduct.set(product): resetParams()
|
|
@ -10,7 +10,7 @@
|
|||
imageView: 'flex m-16 gap-12 w-100',
|
||||
productInfo: 'flex flex-col gap-2',
|
||||
name: 'font-bold text-2xl',
|
||||
description: '',
|
||||
description: 'leading-5',
|
||||
date: 'text-xs w-2/3 mt-auto mb-4',
|
||||
tags: 'flex gap-1',
|
||||
img: 'p-8 border w-72 h-72 min-w-36 min-h-36 border-black border-2 p-3',
|
||||
|
|
|
@ -1,50 +1,55 @@
|
|||
<script>
|
||||
import { currentProduct, productsView } from '$lib/stores';
|
||||
import { toProduct } from '$helpers';
|
||||
import { currentProduct, productsView } from '$lib/stores';
|
||||
import { toProduct } from '$helpers';
|
||||
|
||||
$: foundIndex = $productsView.findIndex((prod) => prod._id === $currentProduct._id)
|
||||
$: PREV = $productsView[foundIndex-1];
|
||||
$: NEXT = $productsView[foundIndex+1];
|
||||
$: foundIndex = $productsView.findIndex((prod) => prod._id === $currentProduct._id);
|
||||
$: PREV = $productsView[foundIndex - 1];
|
||||
$: NEXT = $productsView[foundIndex + 1];
|
||||
|
||||
const navigate = (e) => {
|
||||
if (e.target.name === 'prev' && PREV) {
|
||||
toProduct(PREV, currentProduct)
|
||||
}
|
||||
if (e.target.name === 'next' && NEXT){
|
||||
toProduct(NEXT, currentProduct)
|
||||
}
|
||||
}
|
||||
const navigate = (e) => {
|
||||
if (e.target.name === 'prev' && PREV) {
|
||||
toProduct(PREV, currentProduct);
|
||||
}
|
||||
if (e.target.name === 'next' && NEXT) {
|
||||
toProduct(NEXT, currentProduct);
|
||||
}
|
||||
};
|
||||
|
||||
const { container, btn } = {
|
||||
container: 'flex justify-between h-12 ml-16 mr-16',
|
||||
btn: 'flex items-center gap-4'
|
||||
container: 'flex justify-between m-16 mt-2 mb-6 h-12 ',
|
||||
btn: 'flex items-center gap-4'
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class={container}>
|
||||
{#if PREV }
|
||||
<button on:click={navigate} class={btn} name={'prev'}
|
||||
><svg focusable="false" width={50} height={50} viewBox="0 0 24 24">
|
||||
<path d="m14 7-5 5 5 5V7z" />
|
||||
</svg>{PREV.name}</button
|
||||
>
|
||||
{:else}
|
||||
<div></div>
|
||||
{/if}
|
||||
{#if PREV}
|
||||
<button on:click={navigate} class={btn} name={'prev'}
|
||||
><svg focusable="false" width={50} height={50} viewBox="0 0 24 24">
|
||||
<path d="m14 7-5 5 5 5V7z" />
|
||||
</svg><span class="text-left">{PREV.name}</span></button
|
||||
>
|
||||
{:else}
|
||||
<div />
|
||||
{/if}
|
||||
|
||||
{#if NEXT}
|
||||
<button on:click={navigate} class={btn} name={'next'}
|
||||
>{NEXT.name}<svg focusable="false" width={50} height={50} viewBox="0 0 24 24"
|
||||
><path d="m10 17 5-5-5-5v10z" /></svg
|
||||
></button
|
||||
>
|
||||
{:else}
|
||||
<div></div>
|
||||
{/if}
|
||||
{#if NEXT}
|
||||
<button on:click={navigate} class={btn} name={'next'}
|
||||
><span class="text-right">{NEXT.name}</span><svg
|
||||
focusable="false"
|
||||
width={50}
|
||||
height={50}
|
||||
viewBox="0 0 24 24"><path d="m10 17 5-5-5-5v10z" /></svg
|
||||
></button
|
||||
>
|
||||
{:else}
|
||||
<div />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
svg,path {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
svg,
|
||||
path,
|
||||
span {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
<script>
|
||||
export let tag;
|
||||
import { tags } from '$lib/stores';
|
||||
import { tags, currentProduct, productsView, products, filters } from '$lib/stores';
|
||||
import { filterByCat, resetParams} from '$helpers';
|
||||
|
||||
const tagName = ($tags.find((dirTag) => dirTag._id === tag._ref)).name
|
||||
|
||||
const filter = () => {
|
||||
const updatedByCat = filterByCat($products, tagName, $tags)
|
||||
currentProduct.set({});
|
||||
productsView.set(updatedByCat)
|
||||
filters.set({...$filters, selectedCat: tagName})
|
||||
resetParams()
|
||||
}
|
||||
|
||||
const { container } = {
|
||||
container: 'text-sm flex rounded-md pl-2 pr-2 pt-1 pb-1 bg-gray-200'
|
||||
container: 'text-sm flex rounded-md pl-2 pr-2 pt-1 pb-1 bg-gray-200 hover:bg-blue-300'
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<p class={container}>{tagName}</p>
|
||||
<button class={container} on:click={filter}>{tagName}</button>
|
|
@ -1,48 +1,35 @@
|
|||
<script>
|
||||
import { productsView, products, tags } from '$lib/stores';
|
||||
import {normalize, getAvgRating } from '$helpers';
|
||||
export let filters;
|
||||
import { productsView, products, tags, filters } from '$lib/stores';
|
||||
import { normalize, filterByRating, filterByCat } from '$helpers';
|
||||
export let reset;
|
||||
let { selectedCat, selectedRating } = filters;
|
||||
let { selectedCat, selectedRating } = $filters;
|
||||
|
||||
const stars = ['⭐ ⬆', '⭐⭐ ⬆', '⭐⭐⭐ ⬆', '⭐⭐⭐⭐ ⬆', '⭐⭐⭐⭐⭐ ⬆'];
|
||||
|
||||
const filterByCat = (products, value) =>
|
||||
products.filter((product) => {
|
||||
if (product.tags) {
|
||||
const selectedTagId = $tags.find((tag) => tag.name === value)._id;
|
||||
const productTags = product.tags.map((tag) => tag._ref);
|
||||
if (productTags.includes(selectedTagId)) return true;
|
||||
} else return false;
|
||||
});
|
||||
|
||||
const filterByRating = (products, value) =>
|
||||
products.filter((product) => product.rating && getAvgRating(product.rating) >= Number(value));
|
||||
|
||||
const filter = ({ target: { name, value } }) => {
|
||||
if (Number(value) === 0) {
|
||||
reset();
|
||||
|
||||
return;
|
||||
}
|
||||
switch (normalize(name)) {
|
||||
case 'rating':
|
||||
if (selectedCat)
|
||||
productsView.set(filterByCat(filterByRating($products, value), selectedCat));
|
||||
productsView.set(filterByCat(filterByRating($products, value), selectedCat, $tags));
|
||||
else productsView.set(filterByRating($products, value));
|
||||
break;
|
||||
case 'category':
|
||||
if (selectedRating)
|
||||
productsView.set(filterByRating(filterByCat($products, value), selectedRating));
|
||||
else productsView.set(filterByCat($products, value));
|
||||
productsView.set(filterByRating(filterByCat($products, value, $tags), selectedRating));
|
||||
else productsView.set(filterByCat($products, value, $tags));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const { container, filterBar, filterTitle, filterSort } = {
|
||||
container: 'flex flex-col text-sm h-auto mb-4 mr-6',
|
||||
const { container, filterBar, filterTitle } = {
|
||||
container: 'flex flex-col text-sm h-auto mb-4 mr-6 mt-4',
|
||||
filterBar: 'pl-8 p-2 pr-2 flex justify-between',
|
||||
filterTitle: 'font-bold',
|
||||
filterSort: 'p-1 pl-9 mr-12 hover:bg-blue-300 w-full focus:outline-none'
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -52,14 +39,16 @@
|
|||
<button on:click={reset}> ✖️ </button>
|
||||
</div>
|
||||
|
||||
<select on:change={filter} class={filterSort} name="category" bind:value={filters.selectedCat}>
|
||||
<select on:change={filter} class={`p-1 pl-9 mr-12 hover:bg-blue-300 w-full focus:outline-none ${$filters.selectedCat? 'bg-blue-300':''}`}
|
||||
name="category" bind:value={$filters.selectedCat}>
|
||||
<option select="selected" value={0}>Category</option>
|
||||
{#each $tags as tag (tag.name)}
|
||||
<option value={tag.name}>{tag.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
||||
<select on:change={filter} class={filterSort} name="rating" bind:value={filters.selectedRating}>
|
||||
<select on:change={filter} class={`p-1 pl-9 mr-12 hover:bg-blue-300 w-full focus:outline-none ${$filters.selectedRating? 'bg-blue-300':''}`}
|
||||
name="rating" bind:value={$filters.selectedRating}>
|
||||
<option select="selected" value={0}>Rating</option>
|
||||
{#each stars as starValue, i (starValue)}
|
||||
<option value={i + 1}>{starValue}</option>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
{#each productsView as product}
|
||||
<button class={productStyle} on:click={() => toProduct(product, currentProduct)}>
|
||||
<p
|
||||
class={`pl-12 hover:bg-gray-200 ${
|
||||
class={`pl-10 hover:bg-gray-200 ${
|
||||
$currentProduct && $currentProduct.name === product.name ? 'bg-blue-300' : ''
|
||||
} text-sm`}
|
||||
>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
};
|
||||
|
||||
const { container, input } = {
|
||||
container: 'h-16 ml-auto mr-auto',
|
||||
container: 'h-12 ml-auto mr-auto flex justify-center items-center',
|
||||
input: 'outline outline-1 w-36 focus:outline-blue-600 p-1 text-sm'
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { writable } from "svelte/store";
|
||||
|
||||
export const products = writable([])
|
||||
export const currentProduct = writable({})
|
||||
export const productsView = writable([])
|
||||
export const emotions = writable([])
|
||||
export const tags = writable([])
|
||||
|
||||
export const tags = writable([])
|
||||
|
||||
export const productsView = writable([])
|
||||
export const currentProduct = writable({})
|
||||
export const filters = writable({ selectedCat: 0, selectedRating: 0 })
|
|
@ -1,25 +1,24 @@
|
|||
<script>
|
||||
import '../app.css';
|
||||
import { products, productsView, currentProduct } from '$lib/stores';
|
||||
import { products, productsView, currentProduct, filters } from '$lib/stores';
|
||||
import { defaultFilter, resetParams } from '$helpers';
|
||||
import Products from '$lib/Layout/Products.svelte';
|
||||
import Header from '$lib/Layout/Header.svelte';
|
||||
import Search from '$lib/Layout/Search.svelte';
|
||||
import Filters from '$lib/Layout/Filters.svelte';
|
||||
import Sort from '$lib/Layout/Sort.svelte';
|
||||
let filters = { selectedCat: 0, selectedRating: 0 };
|
||||
|
||||
const reset = () => {
|
||||
productsView.set($products);
|
||||
const url = new URL(window.location);
|
||||
window.history.pushState({}, '', url);
|
||||
resetParams();
|
||||
currentProduct.set({});
|
||||
filters = { selectedCat: 0, selectedRating: 0 };
|
||||
filters.set(structuredClone(defaultFilter));
|
||||
};
|
||||
|
||||
const { main, container, sidebar } = {
|
||||
main: 'flex w-screen h-screen sfmono',
|
||||
container: 'flex flex-col h-screen justify-start shrink-0 overflow-auto w-56',
|
||||
sidebar: 'pt-1 flex flex-col shrink-0'
|
||||
sidebar: ' flex flex-col shrink-0'
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -27,13 +26,13 @@
|
|||
<div class={container}>
|
||||
<Header {reset} />
|
||||
<div class={sidebar}>
|
||||
{#if !Object.keys($currentProduct).length}
|
||||
<Search />
|
||||
<Filters bind:filters {reset} />
|
||||
{#if !Object.keys($currentProduct).length}
|
||||
<Filters {reset} />
|
||||
<Sort />
|
||||
{:else}
|
||||
<Products productsView={$productsView} {currentProduct} />
|
||||
{/if}
|
||||
{:else}
|
||||
<Products productsView={$productsView} {currentProduct}/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<slot />
|
||||
|
|
|
@ -1,43 +1,32 @@
|
|||
<script>
|
||||
import { browser } from '$app/env';
|
||||
import Grid from '$lib/Grid.svelte';
|
||||
import Feature from '$lib/Feature/Feature.svelte';
|
||||
import { products, productsView, tags, currentProduct, emotions } from '$lib/stores';
|
||||
import { browser } from '$app/env';
|
||||
import { normalize, parseSlug } from '$helpers';
|
||||
import { findProdFromParam, getProdParam, resetParams } from '$helpers';
|
||||
|
||||
export let data;
|
||||
|
||||
products.set(data.products);
|
||||
tags.set(data.tags);
|
||||
emotions.set(data.emotions)
|
||||
|
||||
productsView.set(data.products);
|
||||
const goToProduct = () => {
|
||||
const paramProd = (new URLSearchParams(window.location.search)).get('product');
|
||||
tags.set(data.tags);
|
||||
emotions.set(data.emotions);
|
||||
|
||||
if (paramProd) {
|
||||
const foundProduct = $products.find(
|
||||
({ name }) => normalize(name) === normalize(parseSlug(paramProd))
|
||||
);
|
||||
if (foundProduct) {
|
||||
currentProduct.set(foundProduct);
|
||||
}
|
||||
} else {
|
||||
const url = new URL(window.location.origin);
|
||||
history.pushState({}, '', url);
|
||||
currentProduct.set({});
|
||||
}
|
||||
const load = () => {
|
||||
currentProduct.set(findProdFromParam(getProdParam(), $products));
|
||||
if (getProdParam() && JSON.stringify($currentProduct) === '{}') resetParams();
|
||||
};
|
||||
|
||||
if (browser) {
|
||||
goToProduct();
|
||||
load();
|
||||
window.onpopstate = () => {
|
||||
goToProduct();
|
||||
}
|
||||
load();
|
||||
};
|
||||
}
|
||||
|
||||
const { container} = {
|
||||
const { container } = {
|
||||
container: 'h-screen overflow-auto w-screen'
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 537 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
After Width: | Height: | Size: 369 KiB |
|
@ -10,7 +10,7 @@ const config = {
|
|||
}),
|
||||
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
adapter: adapter()
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test('index page has expected h1', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
expect(await page.textContent('main')).toBe('Welcome to SvelteKit');
|
||||
});
|
||||
// test('index page has expected h1', async ({ page }) => {
|
||||
// await page.goto('/');
|
||||
// expect(await page.textContent('main')).toBe('Welcome to SvelteKit');
|
||||
// });
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"$helpers/*": ["src/helpers/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { resolve } from 'path';
|
||||
import { configDefaults } from 'vitest/config';
|
||||
|
||||
/** @type {import('vite').UserConfig} */
|
||||
const config = {
|
||||
|
@ -14,6 +15,27 @@ const config = {
|
|||
fs: {
|
||||
allow: ['backend']
|
||||
}
|
||||
},
|
||||
define: {
|
||||
// Eliminate in-source test code
|
||||
'import.meta.vitest': 'undefined'
|
||||
},
|
||||
test: {
|
||||
// jest like globals
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
// in-source testing
|
||||
includeSource: ['src/**/*.{js,ts,svelte}'],
|
||||
// Exclude files in c8
|
||||
coverage: {
|
||||
exclude: ['setupTest.js', 'src/mocks']
|
||||
},
|
||||
setupFiles: ['./setupTest.js'],
|
||||
deps: {
|
||||
// Put Svelte component here, e.g., inline: [/svelte-multiselect/, /msw/]
|
||||
},
|
||||
// Exclude playwright tests folder
|
||||
exclude: [...configDefaults.exclude, 'tests']
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue