]> git.r.bdr.sh - rbdr/forum/commitdiff
Don't remember what this WIP was about main
authorRuben Beltran del Rio <redacted>
Sun, 24 Dec 2023 11:31:07 +0000 (12:31 +0100)
committerRuben Beltran del Rio <redacted>
Sun, 24 Dec 2023 11:31:07 +0000 (12:31 +0100)
.tool-versions
src/lib/components/tag/tag.svelte
src/lib/data/queries.ts [new file with mode: 0644]
src/lib/stores/apollo.ts [new file with mode: 0644]
src/lib/stores/posts.test.ts
src/lib/stores/tags.test.ts [new file with mode: 0644]

index 826ccf4fef044d30a2c731a83c2537fdd96e3b08..a85118832c036c3e6795854ab0d0c030aea2a742 100644 (file)
@@ -1 +1 @@
-nodejs 16.15.0
+nodejs 20.4.0
index 17440943f64bd1f770400048f65da340e084354c..71e8efff466d54db3d4bfb38cb4da7fecbd7f39a 100644 (file)
@@ -1,7 +1,7 @@
 <script lang="ts">
-       import type { Tag, Topic } from '$lib/data/types';
+       import type { Topic } from '$lib/data/types';
 
-       export let tag: Tag;
+       export let tag: string;
        export let topics: Topic[];
 
        import { _ } from 'svelte-i18n';
diff --git a/src/lib/data/queries.ts b/src/lib/data/queries.ts
new file mode 100644 (file)
index 0000000..7364c0f
--- /dev/null
@@ -0,0 +1,90 @@
+import { gql } from '@apollo/client/core';
+
+export const GET_FORUMS = gql`
+       query GetForums {
+               forums {
+                       id
+                       glyph
+                       label
+                       position
+               }
+       }
+`;
+
+export const GET_FORUM = gql`
+       query GetForum($id: ID!) {
+               forum(id: $id) {
+                       id
+                       glyph
+                       label
+                       position
+                       topics {
+                               id
+                               title
+                               updated_at
+                               ttl
+                       }
+               }
+       }
+`;
+
+export const GET_TAG = gql`
+       query GetTag($id: ID!) {
+               tag(id: $id) {
+                       id
+                       topics {
+                               id
+                               title
+                               updated_at
+                               ttl
+                       }
+               }
+       }
+`;
+
+export const GET_TOPIC = gql`
+       query GetTopic($id: ID!) {
+               topic(id: $id) {
+                       id
+                       title
+                       updated_at
+                       ttl
+                       forum {
+                               id
+                               glyph
+                               label
+                       }
+                       tags {
+                               id
+                               weight
+                       }
+                       posts {
+                               id
+                               text
+                               created_at
+                               author {
+                                       id
+                                       handle
+                               }
+                       }
+               }
+       }
+`;
+
+export const GET_POST = gql`
+       query GetPost($id: ID!) {
+               post(id: $id) {
+                       id
+                       text
+                       created_at
+                       author {
+                               id
+                               handle
+                       }
+                       topic {
+                               id
+                               title
+                       }
+               }
+       }
+`;
diff --git a/src/lib/stores/apollo.ts b/src/lib/stores/apollo.ts
new file mode 100644 (file)
index 0000000..4ef1986
--- /dev/null
@@ -0,0 +1,62 @@
+import { ApolloError } from '@apollo/client/core';
+import { readable } from 'svelte/store';
+import { client } from '$lib/config/apollo';
+import type { DocumentNode, ApolloQueryResult } from '@apollo/client/core';
+
+import type { Readable } from 'svelte/store';
+
+/*
+ * This is a generic store for use with apollo
+ */
+
+type ApolloStoreConfiguration<Type> = {
+       key: string;
+       query: DocumentNode;
+       initialValue?: Type | void;
+       variables?: object;
+};
+
+type ApolloStoreState<Type> = {
+       loading: boolean;
+       data: Type | void;
+       error: Error | void;
+};
+
+export const store = function store<Type>({
+       key,
+       query,
+       initialValue = null,
+       variables = {}
+}: ApolloStoreConfiguration<Type>): Readable<ApolloStoreState<Type>> {
+       const initialState: ApolloStoreState<Type> = {
+               loading: true,
+               data: initialValue,
+               error: undefined
+       };
+
+       return readable(initialState, (set) => {
+               const handleError = function (error: Error) {
+                       return set({
+                               loading: false,
+                               data: initialValue,
+                               error
+                       });
+               };
+
+               client.watchQuery({ query, variables }).subscribe(
+                       (result: ApolloQueryResult<Type>) => {
+                               if (result.errors) {
+                                       const error = new ApolloError({ graphQLErrors: result.errors });
+                                       return handleError(error);
+                               }
+
+                               set({
+                                       loading: false,
+                                       data: result.data[key],
+                                       error: undefined
+                               });
+                       },
+                       (error: Error) => handleError(error)
+               );
+       });
+};
index ef1835d8f1c14052e295f4855e20e84ac294fdf7..df9b57ce5b7f7f1385a77a69698ea068264c9715 100644 (file)
@@ -7,13 +7,13 @@ const { eachLike, like } = Matchers;
 
 jest.mock('$lib/config/config.ts');
 
-import { getPost } from './posts';
+import { post } from './posts';
 
 const internals = {
        provider: null
 };
 
-describe('Posts store pact', () => {
+describe('post', () => {
        beforeAll(async () => {
                internals.provider = new Pact({
                        port: 1234,
@@ -90,7 +90,7 @@ describe('Posts store pact', () => {
                        });
 
                        test('it returns the post', async () => {
-                               const post = getPost('8f75eba5-6989-4dd3-b466-e464546ce374');
+                               const post = post('8f75eba5-6989-4dd3-b466-e464546ce374');
                                const { counter, promise: resolveAfterTwo } = resolveAfter(2);
                                let response = null;
                                post.subscribe((postValue) => {
@@ -169,7 +169,7 @@ describe('Posts store pact', () => {
                        });
 
                        test('it returns the post', async () => {
-                               const post = getPost('8f75eba5-6989-4dd3-b466-e464546ce374');
+                               const post = post('8f75eba5-6989-4dd3-b466-e464546ce374');
                                const { counter, promise: resolveAfterTwo } = resolveAfter(2);
                                let response = null;
                                post.subscribe((postValue) => {
@@ -228,7 +228,7 @@ describe('Posts store pact', () => {
                        });
 
                        test('it returns the error', async () => {
-                               const post = getPost('8f75eba5-6989-4dd3-b466-e464546ce374');
+                               const post = post('8f75eba5-6989-4dd3-b466-e464546ce374');
                                const { counter, promise: resolveAfterTwo } = resolveAfter(2);
                                let response = null;
                                post.subscribe((postValue) => {
@@ -295,7 +295,7 @@ describe('Posts store pact', () => {
                        });
 
                        test('it returns the error', async () => {
-                               const post = getPost('8f75eba5-6989-4dd3-b466-e464546ce374');
+                               const post = post('8f75eba5-6989-4dd3-b466-e464546ce374');
                                const { counter, promise: resolveAfterTwo } = resolveAfter(2);
                                let response = null;
                                post.subscribe((postValue) => {
diff --git a/src/lib/stores/tags.test.ts b/src/lib/stores/tags.test.ts
new file mode 100644 (file)
index 0000000..8c9effe
--- /dev/null
@@ -0,0 +1,295 @@
+import { GraphQLInteraction, Pact, Matchers } from '@pact-foundation/pact';
+import { resolve } from 'path';
+
+import { resolveAfter } from '$lib/utils/resolve_after';
+
+const { eachLike, like } = Matchers;
+
+jest.mock('$lib/config/config.ts');
+
+import { getTag } from './tags';
+
+const internals = {
+       provider: null
+};
+
+describe('Tags store pact', () => {
+       beforeAll(async () => {
+               internals.provider = new Pact({
+                       port: 1234,
+                       dir: resolve(process.cwd(), 'pacts'),
+                       consumer: 'ForumClient',
+                       provider: 'ForumServer',
+                       pactfileWriteMode: 'update'
+               });
+
+               await internals.provider.setup();
+       });
+
+       afterEach(() => internals.provider.verify());
+       afterAll(() => internals.provider.finalize());
+
+       describe("When there's data", () => {
+               describe('GetTag', () => {
+                       beforeAll(async () => {
+                               const tagQuery = new GraphQLInteraction()
+                                       .given("there's data")
+                                       .uponReceiving('a request to get a single tag')
+                                       .withRequest({
+                                               path: '/graphql',
+                                               method: 'POST'
+                                       })
+                                       .withOperation('GetTag')
+                                       .withQuery(
+                                               `query GetTag($id: ID!) {
+              tag(id: $id) {
+                id
+                topics {
+                  id
+                  title
+                  updated_at
+                  ttl
+                  __typename
+                }
+                __typename
+              }
+            }`
+                                       )
+                                       .withVariables({
+                                               id: 'pineapple'
+                                       })
+                                       .willRespondWith({
+                                               status: 200,
+                                               headers: {
+                                                       'Content-Type': 'application/json; charset=utf-8'
+                                               },
+                                               body: {
+                                                       data: {
+                                                               tag: {
+                                                                       id: like('pineapple'),
+                                                                       topics: eachLike({
+                                                                               id: like('cd038ae7-e8b4-4e38-9543-3d697e69ac34'),
+                                                                               title: like('This topic is about pineapples'),
+                                                                               updated_at: like(1619978944077),
+                                                                               ttl: like(3555)
+                                                                       })
+                                                               }
+                                                       }
+                                               }
+                                       });
+                               return await internals.provider.addInteraction(tagQuery);
+                       });
+
+                       test('it returns the tag', async () => {
+                               const tag = getTag('pineapple');
+                               const { counter, promise: resolveAfterTwo } = resolveAfter(2);
+                               let response = null;
+                               tag.subscribe((tagValue) => {
+                                       response = tagValue;
+                                       counter();
+                               });
+                               expect(response.data).toBe(null);
+                               expect(response.loading).toBe(true);
+                               expect(response.error).toBe(undefined);
+                               await resolveAfterTwo;
+                               expect(response.data).toEqual({
+                                       id: 'pineapple',
+                                       topics: [
+                                               {
+                                                       id: 'cd038ae7-e8b4-4e38-9543-3d697e69ac34',
+                                                       title: 'This topic is about pineapples',
+                                                       updated_at: 1619978944077,
+                                                       ttl: 3555
+                                               }
+                                       ]
+                               });
+                               expect(response.loading).toBe(false);
+                               expect(response.error).toBe(undefined);
+                       });
+               });
+       });
+
+       describe("When there's no data", () => {
+               describe('GetTag', () => {
+                       beforeAll(async () => {
+                               const tagQuery = new GraphQLInteraction()
+                                       .given("there's no data")
+                                       .uponReceiving('a request to get a single tag')
+                                       .withRequest({
+                                               path: '/graphql',
+                                               method: 'POST'
+                                       })
+                                       .withOperation('GetTag')
+                                       .withQuery(
+                                               `query GetTag($id: ID!) {
+              tag(id: $id) {
+                id
+                topics {
+                  id
+                  title
+                  updated_at
+                  ttl
+                  __typename
+                }
+                __typename
+              }
+            }`
+                                       )
+                                       .withVariables({
+                                               id: 'pineapple'
+                                       })
+                                       .willRespondWith({
+                                               status: 200,
+                                               headers: {
+                                                       'Content-Type': 'application/json; charset=utf-8'
+                                               },
+                                               body: {
+                                                       data: {
+                                                               tag: null
+                                                       }
+                                               }
+                                       });
+                               return await internals.provider.addInteraction(tagQuery);
+                       });
+
+                       test('it returns the tag', async () => {
+                               const tag = getTag('pineapple');
+                               const { counter, promise: resolveAfterTwo } = resolveAfter(2);
+                               let response = null;
+                               tag.subscribe((tagValue) => {
+                                       response = tagValue;
+                                       counter();
+                               });
+                               expect(response.data).toBe(null);
+                               expect(response.loading).toBe(true);
+                               expect(response.error).toBe(undefined);
+                               await resolveAfterTwo;
+                               expect(response.data).toBe(null);
+                               expect(response.loading).toBe(false);
+                               expect(response.error).toBe(undefined);
+                       });
+               });
+       });
+
+       describe("When there's a server error", () => {
+               describe('GetTag', () => {
+                       beforeAll(async () => {
+                               const tagQuery = new GraphQLInteraction()
+                                       .given("there's a server error")
+                                       .uponReceiving('a request to get a single tag')
+                                       .withRequest({
+                                               path: '/graphql',
+                                               method: 'POST'
+                                       })
+                                       .withOperation('GetTag')
+                                       .withQuery(
+                                               `query GetTag($id: ID!) {
+              tag(id: $id) {
+                id
+                topics {
+                  id
+                  title
+                  updated_at
+                  ttl
+                  __typename
+                }
+                __typename
+              }
+            }`
+                                       )
+                                       .withVariables({
+                                               id: 'pineapple'
+                                       })
+                                       .willRespondWith({
+                                               status: 500
+                                       });
+                               return await internals.provider.addInteraction(tagQuery);
+                       });
+
+                       test('it returns the error', async () => {
+                               const tag = getTag('pineapple');
+                               const { counter, promise: resolveAfterTwo } = resolveAfter(2);
+                               let response = null;
+                               tag.subscribe((tagValue) => {
+                                       response = tagValue;
+                                       counter();
+                               });
+                               expect(response.data).toBe(null);
+                               expect(response.loading).toBe(true);
+                               expect(response.error).toBe(undefined);
+                               await resolveAfterTwo;
+                               expect(response.data).toBe(null);
+                               expect(response.loading).toBe(false);
+                               expect(response.error).toBeInstanceOf(Error);
+                       });
+               });
+       });
+
+       describe("When there's an error in the response", () => {
+               describe('GetTag', () => {
+                       beforeAll(async () => {
+                               const tagQuery = new GraphQLInteraction()
+                                       .given("there's an error in the response")
+                                       .uponReceiving('a request to get a single tag')
+                                       .withRequest({
+                                               path: '/graphql',
+                                               method: 'POST'
+                                       })
+                                       .withOperation('GetTag')
+                                       .withQuery(
+                                               `query GetTag($id: ID!) {
+              tag(id: $id) {
+                id
+                topics {
+                  id
+                  title
+                  updated_at
+                  ttl
+                  __typename
+                }
+                __typename
+              }
+            }`
+                                       )
+                                       .withVariables({
+                                               id: 'pineapple'
+                                       })
+                                       .willRespondWith({
+                                               status: 200,
+                                               headers: {
+                                                       'Content-Type': 'application/json; charset=utf-8'
+                                               },
+                                               body: {
+                                                       errors: eachLike({
+                                                               message: like('An error occurred when fetching the tag')
+                                                       })
+                                               }
+                                       });
+                               return await internals.provider.addInteraction(tagQuery);
+                       });
+
+                       test('it returns the error', async () => {
+                               const tag = getTag('pineapple');
+                               const { counter, promise: resolveAfterTwo } = resolveAfter(2);
+                               let response = null;
+                               tag.subscribe((tagValue) => {
+                                       response = tagValue;
+                                       counter();
+                               });
+                               expect(response.data).toBe(null);
+                               expect(response.loading).toBe(true);
+                               expect(response.error).toBe(undefined);
+                               await resolveAfterTwo;
+                               expect(response.data).toBe(null);
+                               expect(response.loading).toBe(false);
+                               expect(response.error.graphQLErrors).toEqual(
+                                       expect.arrayContaining([
+                                               {
+                                                       message: 'An error occurred when fetching the tag'
+                                               }
+                                       ])
+                               );
+                       });
+               });
+       });
+});