Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/categories module #27

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions packages/js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,57 @@ const activeVariant = getActiveVariant(
Returned object

- activeVariant: The resolved price data for the product and selected options (under a `priceData` field) merged with the matching variant, if found.

### `getCategory`

Fetches a category using the passed-in identifier, which can be either the category's ID or the category's slug.

#### Example

```typescript
const category = getCategory(client, "my-category", {
locale: "en",
currency: "USD",
});
```

#### API

```typescript
const category = getCategory(client, id, requestOptions);
```

- `client` (SwellClient): The client returned from the `init` function.
- `id` (string): Identifier for the category. Can be either the category's slug or the category's id.
- `options`: (object, optional): Options for expanding and setting custom request options for the request.
- `expand` (object, optional): An array of the fields to expand. See [Expandable fields]() for a list of the possible expand options.
- `requestOptions` (object, optional): Overwrites the client options for the current request. Parameters:
- `locale` (string, optional): The requested category's locale.
- `currency` (string, optional): The requested category's currency.
- `sessionToken` (string, optional): The token from the session to be used.

### `getCategoryList`

Returns a paginated list of the store's categories.

#### Example

```typescript
const categories = getCategoryList(client, {
page: 2,
limit: 25,
});
```

#### API

```typescript
const categories = getCategoryList(client, options);
```

- `client`: See SwellClient.
- `options` (object, optional): Options for filtering and paginating the response.
- `requestOptions`: See requestOptions
- `page` (number, optional): For pagination purposes. The categories retrieved will start at the pointer specified by this field.
- `limit` (number, optional): Max number of categories to return per page. Defaults to 15, with a maximum of 100.
- `sort` (string, optional): Field to sort responses by.
2 changes: 1 addition & 1 deletion packages/js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@swell/js",
"version": "0.1.0",
"version": "0.0.2",
"type": "module",
"main": "./dist/index.umd.cjs",
"types": "./dist/index.d.ts",
Expand Down
134 changes: 134 additions & 0 deletions packages/js/src/modules/categories/categories.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { describe, it, vi, expect, afterEach, beforeEach } from "vitest";
import { init } from "client";
import request from "request";
import { getCategory, getCategoryList } from "./categories";

vi.mock("request");

describe("modules/categories", () => {
beforeEach(() => {
vi.mocked(request).mockImplementationOnce(() => Promise.resolve({}));
});

afterEach(() => {
vi.resetModules();
vi.restoreAllMocks();
});

const client = init({ key: "test-key", store: "test-store" });

describe("getCategory", () => {
it('should call request with "GET" and "categories/:id"', async () => {
await getCategory(client, "category-id");

expect(request).toHaveBeenCalledWith(
client,
"GET",
"categories/category-id",
{},
);
});

it('should call request with "GET" and "categories/:slug"', async () => {
await getCategory(client, "category-slug");

expect(request).toHaveBeenCalledWith(
client,
"GET",
"categories/category-slug",
{},
);
});

it("should call request with requestOptions", async () => {
await getCategory(client, "category-id", {
requestOptions: {
sessionToken: "test-session",
locale: "test-locale",
currency: "test-currency",
},
});

expect(request).toHaveBeenCalledWith(
client,
"GET",
"categories/category-id",
{
sessionToken: "test-session",
locale: "test-locale",
currency: "test-currency",
},
);
});

it("should call request with expand", async () => {
await getCategory(client, "category-id", {
expand: ["products", "top"],
});

expect(request).toHaveBeenCalledWith(
client,
"GET",
"categories/category-id",
{
searchParams: {
expand: "products,top",
},
},
);
});
});

describe("getCategoryList", () => {
it('should call request with "GET" and "categories"', async () => {
await getCategoryList(client);

expect(request).toHaveBeenCalledWith(
client,
"GET",
"categories",
expect.any(Object),
);
});

it("should call request with requestOptions", async () => {
await getCategoryList(client, {
requestOptions: {
currency: "EUR",
locale: "es-ES",
},
});

expect(request).toHaveBeenCalledWith(
client,
"GET",
"categories",
expect.objectContaining({
currency: "EUR",
locale: "es-ES",
}),
);
});

it("should call request with pagination options", async () => {
await getCategoryList(client, {
page: 2,
limit: 10,
sort: "name",
});

expect(request).toHaveBeenCalledWith(
client,
"GET",
"categories",
expect.objectContaining({
searchParams: expect.objectContaining({
page: 2,
limit: 10,
sort: "name",
}),
}),
);
});
});
});
60 changes: 60 additions & 0 deletions packages/js/src/modules/categories/categories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import request, { HttpMethod, RequestOptions } from "request";
import type { SwellCamelCaseClient, SwellClient } from "client/types";
import type { PaginatedResponse } from "types/api";
import type { ExpandableField, SwitchCamelCase } from "types/utils";
import type {
GetCategoryExpandOptions,
GetCategoryListOptions,
GetCategoryOptions,
GetCategoryResult,
} from "./types";
import type { Category } from "types/api/categories";

/**
* Fetches a category using the passed-in identifier,
* which can be either the category's ID or the category's slug.
* @param client The client returned from the `init` function.
* @param id Identifier for the category. Can be either the category's slug or the category's id.
* @param options Options for expanding and setting custom request options for the request.
*/
export async function getCategory<
C extends SwellClient | SwellCamelCaseClient,
E extends Array<ExpandableField<GetCategoryExpandOptions>> = [],
>(
client: C,
id: string,
options?: GetCategoryOptions<E>,
): Promise<SwitchCamelCase<C, GetCategoryResult<E>>> {
const requestOptions: RequestOptions = {
...options?.requestOptions,
};

if (options?.expand) {
requestOptions.searchParams = {
expand: options.expand.join(","),
};
}

return request(client, HttpMethod.Get, `categories/${id}`, requestOptions);
}

/**
* Returns a paginated list of the store's categories
* @param client The client returned from the `init` function.
* @param options Options for filtering and paginating the response.
*/
export async function getCategoryList<
C extends SwellClient | SwellCamelCaseClient,
>(
client: C,
options: GetCategoryListOptions = {},
): Promise<SwitchCamelCase<C, PaginatedResponse<Category>>> {
return request(client, HttpMethod.Get, "categories", {
searchParams: {
limit: options.limit,
page: options.page,
sort: options.sort,
},
...options.requestOptions,
});
}
2 changes: 2 additions & 0 deletions packages/js/src/modules/categories/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./categories";
export * from "./types";
29 changes: 29 additions & 0 deletions packages/js/src/modules/categories/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type {
Category,
WithParent,
WithProducts,
WithTop,
} from "types/api/categories";
import type { SwellPaginationOptions } from "types/query";
import type { SwellSessionOptions } from "types/session";
import type { Expand, ExpandableField } from "types/utils";

export type GetCategoryListOptions = {
requestOptions?: SwellSessionOptions;
} & SwellPaginationOptions;

export type GetCategoryExpandOptions = "parent" | "products" | "top";

export type GetCategoryOptions<
E extends Array<ExpandableField<GetCategoryExpandOptions>>,
> = {
expand?: E;
requestOptions?: SwellSessionOptions;
};

export type GetCategoryResult<
E extends Array<ExpandableField<GetCategoryExpandOptions>> = [],
> = Category &
Expand<"parent", E, WithParent<Category>> &
Expand<"products", E, WithProducts<Category>> &
Expand<"top", E, WithTop<Category>>;
15 changes: 15 additions & 0 deletions packages/js/src/types/api/categories/expanded.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Category, LImitedCategory } from ".";
import type { PaginatedResponse } from "types/api";
import type { Product } from "types/api/products";

export type WithParent<T extends Category> = T & {
parent?: LImitedCategory | null;
};

export type WithProducts<T extends Category> = T & {
products?: PaginatedResponse<Product> | null;
};

export type WithTop<T extends Category> = T & {
top?: LImitedCategory | null;
};
19 changes: 19 additions & 0 deletions packages/js/src/types/api/categories/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { BaseModel, Content, PaginatedResponse, Image } from "types/api";

export * from "./expanded";

export interface Category extends BaseModel {
top_id?: string | null;
slug?: string | null;
name?: string | null;
content?: Content | null;
parent_id?: string | null;
meta_title?: string | null;
meta_description?: string | null;
images?: Image[] | null;
description?: string | null;
id?: string | null;
children?: PaginatedResponse<Category> | null;
}

export type LImitedCategory = Pick<Category, "top_id" | "id" | "name" | "slug">;
26 changes: 23 additions & 3 deletions packages/js/src/types/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,27 @@ export interface PaginatedResponse<T> {
}

export interface BaseModel {
id?: string;
date_created?: string;
date_updated?: string;
id?: string | null;
date_created?: string | null;
date_updated?: string | null;
}

export type Content = Record<string, unknown>;
export type Attributes = Record<string, unknown>;

export interface Image {
file?: File | null;
id?: string | null;
}

export interface File {
content_type?: string | null;
date_uploaded?: string | null;
filename?: string | null;
height?: number | null;
id?: string | null;
length?: number | null;
md5?: string | null;
url?: string | null;
width?: number | null;
}
11 changes: 11 additions & 0 deletions packages/js/src/types/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,14 @@ export type SwitchCamelCase<
C extends SwellClient | SwellCamelCaseClient,
T extends object | undefined | null,
> = C extends SwellCamelCaseClient ? CamelCase<T> : T;

type StripExpandedAmount<T extends ExpandableField<string>> =
T extends `${infer K}:${number}` ? K : T;

export type ExpandableField<T extends string> = T | `${T}:${number}`;

export type Expand<
K extends string,
E extends any[],
T extends object,
> = K extends StripExpandedAmount<E[number]> ? T : unknown;
Loading