diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..2e02949 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,31 @@ +# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: Testing CI + +on: + push: + branches: [ "dev", "main" ] + pull_request: + branches: [ "dev", "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm install + - run: npm run test:unit + diff --git a/src/helpers/filters.ts b/src/helpers/filters.ts index 8894e81..e83f726 100644 --- a/src/helpers/filters.ts +++ b/src/helpers/filters.ts @@ -1,23 +1,42 @@ -import { getAvgRating } from '$helpers'; -import type { Product, Tag } from '$types'; +import { avgRating } from '$helpers'; +import type { FullProduct, Tag } from '$types'; + +export type TagArgs = { + value: string; + $tags: Tag[]; +}; + +export type RatingArgs = { + value: string | number; +}; /** Default value for filter selectors, saved in store */ export const defaultFilter = { selectedCat: 0, selectedRating: 0 }; -/** Filters products by matching tags according to passed value */ -export const filterByCat = (products: Product[], value: string, tags: Tag[]): Product[] => +/** Rating greater than what is passed in */ +export const _ratingHigher = (product: FullProduct, { value }: RatingArgs) => + product.rating && avgRating(product.rating) >= Number(value); + +/** Matches tags according to passed value */ +export const _matchesTag = (product: FullProduct, { value, $tags }: TagArgs): boolean => { + try { + 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; + } catch { + return true; + } +}; + +/** Filters products */ +export const filterProductsBy = (type: string, products: FullProduct[], config) => products.filter((product) => { - try { - 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; - } catch { - return true; + switch (type) { + case 'tag': + return _matchesTag(product, config); + case 'rating': + return _ratingHigher(product, config); } }); - -/** Filters by rating greater than what is passed in */ -export const filterByRating = (products: Product[], value: string | number) => - products.filter((product) => product.rating && getAvgRating(product.rating) >= Number(value)); diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 4f23bb4..c42af27 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -1,5 +1,5 @@ export * from './toProduct'; -export * from './parse'; -export * from './getAvgRating'; +export * from './utils/parse'; +export * from './utils/avgRating'; export * from './param'; -export * from './filters' \ No newline at end of file +export * from './filters'; diff --git a/src/helpers/tests/filters.test.ts b/src/helpers/tests/filters.test.ts index b582ac4..5f2e391 100644 --- a/src/helpers/tests/filters.test.ts +++ b/src/helpers/tests/filters.test.ts @@ -1,22 +1,26 @@ -import { filterByCat, filterByRating } from '$helpers'; +import { filterProductsBy } from '$helpers'; import { describe, expect, test } from 'vitest'; import { testProducts, testTags } from './data'; -describe('filterByCat', () => { +describe('filterProducts', () => { test('filters products by passed category tag', () => { - expect(filterByCat(testProducts, 'Exercise', testTags)).toHaveLength(1); - expect(filterByCat(testProducts, 'Electronics', testTags)).toHaveLength(1); + expect( + filterProductsBy('tag', testProducts, { value: 'Exercise', $tags: testTags }) + ).toHaveLength(1); + expect( + filterProductsBy('tag', testProducts, { value: 'Electronics', $tags: testTags }) + ).toHaveLength(1); }); - test('returns unchanged products if tag does not exist', () => { - expect(filterByCat(testProducts, 'grapes', testTags)).toEqual(testProducts); - }); -}); - -describe('filterByRating', () => { - test('filterByRating returns products only greater than # passed in', () => { - expect(filterByRating(testProducts, 3)).toHaveLength(3); - - expect(filterByRating(testProducts, 5)).toHaveLength(0); + test('filter by tag returns unchanged products if tag does not exist', () => { + expect(filterProductsBy('tag', testProducts, { value: 'grapes', $tags: testTags })).toEqual( + testProducts + ); + }); + + test('filters ratings only greater than # passed in', () => { + expect(filterProductsBy('rating', testProducts, { value: 3 })).toHaveLength(3); + + expect(filterProductsBy('rating', testProducts, { value: 5 })).toHaveLength(0); }); }); diff --git a/src/helpers/tests/getAvgRating.test.ts b/src/helpers/tests/getAvgRating.test.ts index a05eb7d..8413b08 100644 --- a/src/helpers/tests/getAvgRating.test.ts +++ b/src/helpers/tests/getAvgRating.test.ts @@ -1,21 +1,19 @@ -import { getAvgRating } from '$helpers'; +import { avgRating } from '$helpers'; import { test, expect } from 'vitest'; import { testRating1, testRating2 } from './data'; test('return false if no parameters are passed', async () => { - expect(getAvgRating()).toBeFalsy(); + expect(avgRating()).toBeFalsy(); }); test('return false if array with empty object is passed', async () => { - expect(getAvgRating([{}])).toBeFalsy(); + expect(avgRating([{}])).toBeFalsy(); }); test('return single rating if only one rating is passed', async () => { - expect(getAvgRating([testRating1])).toBe(testRating1.rating); + expect(avgRating([testRating1])).toBe(testRating1.rating); }); test('return average of 2 ratings passed', async () => { - expect(getAvgRating([testRating1, testRating2])).toBe( - (testRating1.rating + testRating2.rating) / 2 - ); + expect(avgRating([testRating1, testRating2])).toBe((testRating1.rating + testRating2.rating) / 2); }); diff --git a/src/helpers/getAvgRating.ts b/src/helpers/utils/avgRating.ts similarity index 80% rename from src/helpers/getAvgRating.ts rename to src/helpers/utils/avgRating.ts index 83ea8f7..8f68808 100644 --- a/src/helpers/getAvgRating.ts +++ b/src/helpers/utils/avgRating.ts @@ -1,7 +1,7 @@ import type { Rating } from '$types'; /** Average of all ratings of a product */ -export const getAvgRating = (ratings: Rating[]): number => { +export const avgRating = (ratings: Rating[]): number => { if (!ratings) return 0; if (ratings.length === 1) return ratings[0].rating; else return ratings.reduce((total, curr) => total + curr.rating, 0) / ratings.length; diff --git a/src/helpers/parse.ts b/src/helpers/utils/parse.ts similarity index 100% rename from src/helpers/parse.ts rename to src/helpers/utils/parse.ts diff --git a/src/lib/Feature/Feature.svelte b/src/lib/Feature/Feature.svelte index 9f1c945..4cc2163 100644 --- a/src/lib/Feature/Feature.svelte +++ b/src/lib/Feature/Feature.svelte @@ -4,55 +4,42 @@ import Rating from './Rating/Rating.svelte'; import PrevNext from './PrevNext/PrevNext.svelte'; import Tag from './Tag/Tag.svelte'; - - const { - container, - imageView, - subname, - description, - productInfo, - date, - tags, - img, - ratings - } = { - container: 'flex flex-col', - imageView: 'flex m-16 gap-12 w-100', - productInfo: 'flex flex-col gap-2', - subname: '-mb-2 font-light text-sm', - description: 'leading-5', - date: 'text-xs w-2/3 mt-auto mb-4', - tags: 'flex gap-1 mb-1', - img: 'p-8 border w-72 h-72 min-w-36 min-h-36 border-black border-2 p-3', - ratings: 'w-100' - }; -
-
+
+
{#if $currentProduct.image} - {$currentProduct.name} + {$currentProduct.name} {/if} -
+
{#if $currentProduct.subname} - {$currentProduct.subname} + {$currentProduct.subname} {/if} - {$currentProduct.name} + {$currentProduct.name} {#if $currentProduct.tags} -
+
{#each $currentProduct.tags as tag (tag._ref)} {/each}
{/if} {#if $currentProduct.description} -

{$currentProduct.description}

+

{$currentProduct.description}

{/if} -

Created on: {new Date($currentProduct._createdAt)}

+

Created on: {new Date($currentProduct._createdAt)}

{#if $currentProduct.rating} -
+
{#each $currentProduct.rating as rating (rating._key)} {/each} @@ -61,12 +48,31 @@
- diff --git a/src/lib/Feature/Tag/Tag.svelte b/src/lib/Feature/Tag/Tag.svelte index 11a02c5..be2868e 100644 --- a/src/lib/Feature/Tag/Tag.svelte +++ b/src/lib/Feature/Tag/Tag.svelte @@ -1,28 +1,27 @@ - + - diff --git a/src/lib/Layout/Filters.svelte b/src/lib/Layout/Filters.svelte index f5b555e..fdbaf0e 100644 --- a/src/lib/Layout/Filters.svelte +++ b/src/lib/Layout/Filters.svelte @@ -1,48 +1,61 @@ -
-
-
Filter
+
+
+
Filter
- diff --git a/src/types/index.ts b/src/types/index.ts index 341d475..d9f6c77 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -46,8 +46,23 @@ export interface Product { description: string; image?: Image; name: string; - rating?: Ref[] | Rating[]; - tags?: Ref[] | Tag[]; + rating?: Ref[]; + tags?: Ref[]; + url?: string; + subname?: string; +} + +export interface FullProduct { + _createdAt: string; + _id: string; + _rev: string; + _type: 'product'; + _updatedAt: string; + description: string; + image?: Image; + name: string; + rating?: Rating[]; + tags?: Ref[]; url?: string; subname?: string; }