diff --git a/.codegen.json b/.codegen.json index e41b91288..f10b8e294 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "4de40e1", "specHash": "e0ffc4a", "version": "10.10.0" } +{ "engineHash": "5e9332b", "specHash": "d028758", "version": "10.10.0" } diff --git a/.github/workflows/build_and_test_browser.yml b/.github/workflows/build_and_test_browser.yml index 807aad8cd..d503bbcea 100644 --- a/.github/workflows/build_and_test_browser.yml +++ b/.github/workflows/build_and_test_browser.yml @@ -58,7 +58,7 @@ jobs: run: | cd test-browser npm install - npx start-server-and-test build-run 3000 'cypress run --browser ${{ matrix.browser }}' + npx start-server-and-test 'npm run build-run' http://localhost:3000 'cypress run --browser ${{ matrix.browser }}' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} JWT_CONFIG_BASE_64: ${{ secrets.JWT_CONFIG_BASE_64 }} @@ -83,7 +83,7 @@ jobs: run: | cd test-browser npm install - npx start-server-and-test build-run 3000 'cypress run --browser ${{ matrix.browser }}' + npx start-server-and-test 'npm run build-run' http://localhost:3000 'cypress run --browser ${{ matrix.browser }}' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} JWT_CONFIG_BASE_64: ${{ secrets.JWT_CONFIG_BASE_64 }} diff --git a/.github/workflows/build_and_test_browser_daily.yml b/.github/workflows/build_and_test_browser_daily.yml index 841c9d111..afd728479 100644 --- a/.github/workflows/build_and_test_browser_daily.yml +++ b/.github/workflows/build_and_test_browser_daily.yml @@ -30,7 +30,7 @@ jobs: run: | cd test-browser npm install - npx start-server-and-test build-run 3000 'cypress run --browser ${{ matrix.browser }}' + npx start-server-and-test 'npm run build-run' http://localhost:3000 'cypress run --browser ${{ matrix.browser }}' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} JWT_CONFIG_BASE_64: ${{ secrets.JWT_CONFIG_BASE_64 }} diff --git a/docs/README.md b/docs/README.md index 2bafde957..47380abd3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -61,6 +61,7 @@ the SDK are available by topic: - [Metadata cascade policies](metadataCascadePolicies.md) - [Metadata taxonomies](metadataTaxonomies.md) - [Metadata templates](metadataTemplates.md) +- [Notes](notes.md) - [Recent items](recentItems.md) - [Retention policies](retentionPolicies.md) - [Retention policy assignments](retentionPolicyAssignments.md) diff --git a/docs/notes.md b/docs/notes.md new file mode 100644 index 000000000..0f872c103 --- /dev/null +++ b/docs/notes.md @@ -0,0 +1,26 @@ +# NotesManager + +- [Convert content to Box Note](#convert-content-to-box-note) + +## Convert content to Box Note + +Creates a Box Note (`.boxnote` file) from supported source content. See the `content_format` field for supported formats. + +This operation is performed by calling function `createNoteConvertV2026R0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2026.0/post-notes-convert/). + +_Currently we don't have an example for calling `createNoteConvertV2026R0` in integration tests_ + +### Arguments + +- requestBodyInput `NotesConvertRequestBodyV2026R0Input` + - Request body of createNoteConvertV2026R0 method +- optionalsInput `CreateNoteConvertV2026R0OptionalsInput` + +### Returns + +This function returns a value of type `NotesConvertResponseV2026R0`. + +The note was created successfully. diff --git a/package-lock.json b/package-lock.json index ecb91cd00..7108e5cc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1549,30 +1549,6 @@ "@babel/types": "^7.28.2" } }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -1637,9 +1613,9 @@ "peer": true }, "node_modules/@types/node": { - "version": "25.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.0.tgz", - "integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==", + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", "dev": true, "license": "MIT", "dependencies": { @@ -2766,9 +2742,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.359", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.359.tgz", - "integrity": "sha512-8lPELWuYZIWk7NDvCNthtmMw/7Q5Wu25NpM4djFMHBmk8DubPAtL4YTOp7ou0e7HyJtwkVlWv8XMLURnrtgJQw==", + "version": "1.5.360", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.360.tgz", + "integrity": "sha512-GkcBt6YYAw9SxFWn+xVar4cLVGlXVuswwtRLBozi2zp0GjXs4ZnOrqV4zbXzg35n7w81hCkyJNYicgXlVHAmBA==", "dev": true, "license": "ISC" }, @@ -2793,9 +2769,9 @@ "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.21.5", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.5.tgz", - "integrity": "sha512-mLCNbrQli11K1ySUmuNt4ZUB3OpGIDq4q2vTBTf5cL2lpsRjI9QKqSD0ndjW8FyvcW/Jj46gMe9syyHAsvMa/A==", + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4878,11 +4854,14 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.44", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz", - "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", + "version": "2.0.45", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.45.tgz", + "integrity": "sha512-iIbHXV9eBB2nB0wa7oTsrrXq+qQt+9SIlx9AX3T96YgobtEQfis5n6TJ6vV+3QP8DwdriEAcGhARaFCu37peBg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/normalize-path": { "version": "3.0.0", @@ -5967,9 +5946,9 @@ "license": "MIT" }, "node_modules/ts-jest": { - "version": "29.4.10", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.10.tgz", - "integrity": "sha512-vMTlTTtvz5aKZgzOoc7DQ5TzAL2fCzl8JnG1+ZpwjQa/g0xLlwE44yQ+1Cao9ZP1xVv9y5g34IFXEiqGOGFBUA==", + "version": "29.4.11", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.11.tgz", + "integrity": "sha512-IrFl7l9AuB/qrNw5quqvAv/hmKMb8dhWOH4jQOGo0Oq8tCeo1O86/iTFG1FaRimgUkF13l4PcepO8ATFT6Ns4g==", "dev": true, "license": "MIT", "dependencies": { @@ -6266,14 +6245,13 @@ "license": "BSD-2-Clause" }, "node_modules/webpack": { - "version": "5.106.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.2.tgz", - "integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==", + "version": "5.107.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.107.1.tgz", + "integrity": "sha512-mvdIWxj/H6QsfgDdH9djne3a5dYcmEmtsXGESkypaGN5jXjF/b+9KDlmTDQ2TKlFUeA2fI9Y65kihD30JOdB+Q==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", @@ -6283,20 +6261,20 @@ "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.20.0", - "es-module-lexer": "^2.0.0", + "enhanced-resolve": "^5.21.4", + "es-module-lexer": "^2.1.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", - "loader-runner": "^4.3.1", + "loader-runner": "^4.3.2", "mime-db": "^1.54.0", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.17", + "terser-webpack-plugin": "^5.5.0", "watchpack": "^2.5.1", - "webpack-sources": "^3.3.4" + "webpack-sources": "^3.4.1" }, "bin": { "webpack": "bin/webpack.js" diff --git a/src/client.ts b/src/client.ts index cc3e2366b..069fc6962 100644 --- a/src/client.ts +++ b/src/client.ts @@ -86,6 +86,7 @@ import { ShieldListsManager } from './managers/shieldLists'; import { ArchivesManager } from './managers/archives'; import { ExternalUsersManager } from './managers/externalUsers'; import { AutomateWorkflowsManager } from './managers/automateWorkflows'; +import { NotesManager } from './managers/notes'; import { Authentication } from './networking/auth'; import { NetworkSession } from './networking/network'; import { BoxSdkError } from './box/errors'; @@ -192,6 +193,7 @@ export class BoxClient { readonly archives: ArchivesManager; readonly externalUsers: ExternalUsersManager; readonly automateWorkflows: AutomateWorkflowsManager; + readonly notes: NotesManager; constructor( fields: Omit< BoxClient, @@ -279,6 +281,7 @@ export class BoxClient { | 'archives' | 'externalUsers' | 'automateWorkflows' + | 'notes' | 'networkSession' | 'makeRequest' | 'withAsUserHeader' @@ -640,6 +643,10 @@ export class BoxClient { auth: this.auth, networkSession: this.networkSession, }); + this.notes = new NotesManager({ + auth: this.auth, + networkSession: this.networkSession, + }); } /** * Make a custom http request using the client authentication and network session. diff --git a/src/managers/index.ts b/src/managers/index.ts index 0bd25dff9..dd10eb835 100644 --- a/src/managers/index.ts +++ b/src/managers/index.ts @@ -82,3 +82,4 @@ export * from './shieldLists'; export * from './archives'; export * from './externalUsers'; export * from './automateWorkflows'; +export * from './notes'; diff --git a/src/managers/notes.ts b/src/managers/notes.ts new file mode 100644 index 000000000..3a833b3b2 --- /dev/null +++ b/src/managers/notes.ts @@ -0,0 +1,164 @@ +import { serializeNotesConvertResponseV2026R0 } from '../schemas/v2026R0/notesConvertResponseV2026R0'; +import { deserializeNotesConvertResponseV2026R0 } from '../schemas/v2026R0/notesConvertResponseV2026R0'; +import { serializeClientErrorV2026R0 } from '../schemas/v2026R0/clientErrorV2026R0'; +import { deserializeClientErrorV2026R0 } from '../schemas/v2026R0/clientErrorV2026R0'; +import { serializeBoxVersionHeaderV2026R0 } from '../parameters/v2026R0/boxVersionHeaderV2026R0'; +import { deserializeBoxVersionHeaderV2026R0 } from '../parameters/v2026R0/boxVersionHeaderV2026R0'; +import { serializeNotesConvertRequestBodyV2026R0 } from '../schemas/v2026R0/notesConvertRequestBodyV2026R0'; +import { deserializeNotesConvertRequestBodyV2026R0 } from '../schemas/v2026R0/notesConvertRequestBodyV2026R0'; +import { NotesConvertRequestBodyV2026R0Input } from '../schemas/v2026R0/notesConvertRequestBodyV2026R0'; +import { ResponseFormat } from '../networking/fetchOptions'; +import { NotesConvertResponseV2026R0 } from '../schemas/v2026R0/notesConvertResponseV2026R0'; +import { ClientErrorV2026R0 } from '../schemas/v2026R0/clientErrorV2026R0'; +import { BoxVersionHeaderV2026R0 } from '../parameters/v2026R0/boxVersionHeaderV2026R0'; +import { NotesConvertRequestBodyV2026R0 } from '../schemas/v2026R0/notesConvertRequestBodyV2026R0'; +import { BoxSdkError } from '../box/errors'; +import { Authentication } from '../networking/auth'; +import { NetworkSession } from '../networking/network'; +import { FetchOptions } from '../networking/fetchOptions'; +import { FetchResponse } from '../networking/fetchResponse'; +import { prepareParams } from '../internal/utils'; +import { toString } from '../internal/utils'; +import { ByteStream } from '../internal/utils'; +import { CancellationToken } from '../internal/utils'; +import { sdToJson } from '../serialization/json'; +import { SerializedData } from '../serialization/json'; +import { sdIsEmpty } from '../serialization/json'; +import { sdIsBoolean } from '../serialization/json'; +import { sdIsNumber } from '../serialization/json'; +import { sdIsString } from '../serialization/json'; +import { sdIsList } from '../serialization/json'; +import { sdIsMap } from '../serialization/json'; +export class CreateNoteConvertV2026R0Optionals { + readonly headers: CreateNoteConvertV2026R0Headers = + new CreateNoteConvertV2026R0Headers({}); + readonly cancellationToken?: CancellationToken = void 0; + constructor( + fields: Omit< + CreateNoteConvertV2026R0Optionals, + 'headers' | 'cancellationToken' + > & + Partial< + Pick + >, + ) { + if (fields.headers !== undefined) { + this.headers = fields.headers; + } + if (fields.cancellationToken !== undefined) { + this.cancellationToken = fields.cancellationToken; + } + } +} +export interface CreateNoteConvertV2026R0OptionalsInput { + readonly headers?: CreateNoteConvertV2026R0Headers; + readonly cancellationToken?: CancellationToken; +} +export class CreateNoteConvertV2026R0Headers { + /** + * Version header. */ + readonly boxVersion: BoxVersionHeaderV2026R0 = + '2026.0' as BoxVersionHeaderV2026R0; + /** + * Extra headers that will be included in the HTTP request. */ + readonly extraHeaders?: { + readonly [key: string]: undefined | string; + } = {}; + constructor( + fields: Omit< + CreateNoteConvertV2026R0Headers, + 'boxVersion' | 'extraHeaders' + > & + Partial< + Pick + >, + ) { + if (fields.boxVersion !== undefined) { + this.boxVersion = fields.boxVersion; + } + if (fields.extraHeaders !== undefined) { + this.extraHeaders = fields.extraHeaders; + } + } +} +export interface CreateNoteConvertV2026R0HeadersInput { + /** + * Version header. */ + readonly boxVersion?: BoxVersionHeaderV2026R0; + /** + * Extra headers that will be included in the HTTP request. */ + readonly extraHeaders?: { + readonly [key: string]: undefined | string; + }; +} +export class NotesManager { + readonly auth?: Authentication; + readonly networkSession: NetworkSession = new NetworkSession({}); + constructor( + fields: Omit & + Partial>, + ) { + if (fields.auth !== undefined) { + this.auth = fields.auth; + } + if (fields.networkSession !== undefined) { + this.networkSession = fields.networkSession; + } + } + /** + * Creates a Box Note (`.boxnote` file) from supported source content. See the `content_format` field for supported formats. + * @param {NotesConvertRequestBodyV2026R0Input} requestBodyInput Request body of createNoteConvertV2026R0 method + * @param {CreateNoteConvertV2026R0OptionalsInput} optionalsInput + * @returns {Promise} + */ + async createNoteConvertV2026R0( + requestBodyInput: NotesConvertRequestBodyV2026R0Input, + optionalsInput: CreateNoteConvertV2026R0OptionalsInput = {}, + ): Promise { + const requestBody: NotesConvertRequestBodyV2026R0 = + new NotesConvertRequestBodyV2026R0({ + content: requestBodyInput.content, + contentFormat: requestBodyInput.contentFormat, + parent: requestBodyInput.parent, + name: requestBodyInput.name, + }); + const optionals: CreateNoteConvertV2026R0Optionals = + new CreateNoteConvertV2026R0Optionals({ + headers: optionalsInput.headers, + cancellationToken: optionalsInput.cancellationToken, + }); + const headers: any = optionals.headers; + const cancellationToken: any = optionals.cancellationToken; + const headersMap: { + readonly [key: string]: string; + } = prepareParams({ + ...{ ['box-version']: toString(headers.boxVersion) as string }, + ...headers.extraHeaders, + }); + const response: FetchResponse = + await this.networkSession.networkClient.fetch( + new FetchOptions({ + url: ''.concat( + this.networkSession.baseUrls.baseUrl, + '/2.0/notes/convert', + ) as string, + method: 'POST', + headers: headersMap, + data: serializeNotesConvertRequestBodyV2026R0(requestBody), + contentType: 'application/json', + responseFormat: 'json' as ResponseFormat, + auth: this.auth, + networkSession: this.networkSession, + cancellationToken: cancellationToken, + }), + ); + return { + ...deserializeNotesConvertResponseV2026R0(response.data!), + rawData: response.data!, + }; + } +} +export interface NotesManagerInput { + readonly auth?: Authentication; + readonly networkSession?: NetworkSession; +} diff --git a/src/schemas/signRequest.ts b/src/schemas/signRequest.ts index e062fd067..8cab7ea6e 100644 --- a/src/schemas/signRequest.ts +++ b/src/schemas/signRequest.ts @@ -36,6 +36,7 @@ export type SignRequestStatusField = | 'signed' | 'cancelled' | 'declined' + | 'error' | 'error_converting' | 'error_sending' | 'expired' @@ -151,6 +152,9 @@ export function deserializeSignRequestStatusField( if (val == 'declined') { return val; } + if (val == 'error') { + return val; + } if (val == 'error_converting') { return val; } diff --git a/src/schemas/v2026R0/folderReferenceV2026R0.ts b/src/schemas/v2026R0/folderReferenceV2026R0.ts new file mode 100644 index 000000000..c65c82d8d --- /dev/null +++ b/src/schemas/v2026R0/folderReferenceV2026R0.ts @@ -0,0 +1,132 @@ +import { BoxSdkError } from '../../box/errors'; +import { SerializedData } from '../../serialization/json'; +import { sdIsEmpty } from '../../serialization/json'; +import { sdIsBoolean } from '../../serialization/json'; +import { sdIsNumber } from '../../serialization/json'; +import { sdIsString } from '../../serialization/json'; +import { sdIsList } from '../../serialization/json'; +import { sdIsMap } from '../../serialization/json'; +export type FolderReferenceV2026R0TypeField = 'folder'; +export class FolderReferenceV2026R0 { + /** + * The value will always be `folder`. */ + readonly type: FolderReferenceV2026R0TypeField = + 'folder' as FolderReferenceV2026R0TypeField; + /** + * ID of the folder. */ + readonly id!: string; + readonly rawData?: SerializedData; + constructor( + fields: Omit & + Partial>, + ) { + if (fields.type !== undefined) { + this.type = fields.type; + } + if (fields.id !== undefined) { + this.id = fields.id; + } + if (fields.rawData !== undefined) { + this.rawData = fields.rawData; + } + } +} +export interface FolderReferenceV2026R0Input { + /** + * The value will always be `folder`. */ + readonly type?: FolderReferenceV2026R0TypeField; + /** + * ID of the folder. */ + readonly id: string; + readonly rawData?: SerializedData; +} +export function serializeFolderReferenceV2026R0TypeField( + val: FolderReferenceV2026R0TypeField, +): SerializedData { + return val; +} +export function deserializeFolderReferenceV2026R0TypeField( + val: SerializedData, +): FolderReferenceV2026R0TypeField { + if (val == 'folder') { + return val; + } + throw new BoxSdkError({ + message: "Can't deserialize FolderReferenceV2026R0TypeField", + }); +} +export function serializeFolderReferenceV2026R0( + val: FolderReferenceV2026R0, +): SerializedData { + return { + ['type']: serializeFolderReferenceV2026R0TypeField(val.type), + ['id']: val.id, + }; +} +export function deserializeFolderReferenceV2026R0( + val: SerializedData, +): FolderReferenceV2026R0 { + if (!sdIsMap(val)) { + throw new BoxSdkError({ + message: 'Expecting a map for "FolderReferenceV2026R0"', + }); + } + if (val.type == void 0) { + throw new BoxSdkError({ + message: + 'Expecting "type" of type "FolderReferenceV2026R0" to be defined', + }); + } + const type: FolderReferenceV2026R0TypeField = + deserializeFolderReferenceV2026R0TypeField(val.type); + if (val.id == void 0) { + throw new BoxSdkError({ + message: 'Expecting "id" of type "FolderReferenceV2026R0" to be defined', + }); + } + if (!sdIsString(val.id)) { + throw new BoxSdkError({ + message: 'Expecting string for "id" of type "FolderReferenceV2026R0"', + }); + } + const id: string = val.id; + return { type: type, id: id } satisfies FolderReferenceV2026R0; +} +export function serializeFolderReferenceV2026R0Input( + val: FolderReferenceV2026R0Input, +): SerializedData { + return { + ['type']: + val.type == void 0 + ? val.type + : serializeFolderReferenceV2026R0TypeField(val.type), + ['id']: val.id, + }; +} +export function deserializeFolderReferenceV2026R0Input( + val: SerializedData, +): FolderReferenceV2026R0Input { + if (!sdIsMap(val)) { + throw new BoxSdkError({ + message: 'Expecting a map for "FolderReferenceV2026R0Input"', + }); + } + const type: undefined | FolderReferenceV2026R0TypeField = + val.type == void 0 + ? void 0 + : deserializeFolderReferenceV2026R0TypeField(val.type); + if (val.id == void 0) { + throw new BoxSdkError({ + message: + 'Expecting "id" of type "FolderReferenceV2026R0Input" to be defined', + }); + } + if (!sdIsString(val.id)) { + throw new BoxSdkError({ + message: + 'Expecting string for "id" of type "FolderReferenceV2026R0Input"', + }); + } + const id: string = val.id; + return { type: type, id: id } satisfies FolderReferenceV2026R0Input; +} diff --git a/src/schemas/v2026R0/index.ts b/src/schemas/v2026R0/index.ts index ad6b8e4a9..64d753c2e 100644 --- a/src/schemas/v2026R0/index.ts +++ b/src/schemas/v2026R0/index.ts @@ -1,6 +1,9 @@ export * from './automateWorkflowReferenceV2026R0'; export * from './automateWorkflowStartRequestV2026R0'; export * from './clientErrorV2026R0'; +export * from './folderReferenceV2026R0'; +export * from './notesConvertRequestBodyV2026R0'; +export * from './notesConvertResponseV2026R0'; export * from './userBaseV2026R0'; export * from './userMiniV2026R0'; export * from './automateWorkflowActionV2026R0'; diff --git a/src/schemas/v2026R0/notesConvertRequestBodyV2026R0.ts b/src/schemas/v2026R0/notesConvertRequestBodyV2026R0.ts new file mode 100644 index 000000000..84addb6b4 --- /dev/null +++ b/src/schemas/v2026R0/notesConvertRequestBodyV2026R0.ts @@ -0,0 +1,226 @@ +import { serializeFolderReferenceV2026R0 } from './folderReferenceV2026R0'; +import { deserializeFolderReferenceV2026R0 } from './folderReferenceV2026R0'; +import { FolderReferenceV2026R0 } from './folderReferenceV2026R0'; +import { BoxSdkError } from '../../box/errors'; +import { SerializedData } from '../../serialization/json'; +import { sdIsEmpty } from '../../serialization/json'; +import { sdIsBoolean } from '../../serialization/json'; +import { sdIsNumber } from '../../serialization/json'; +import { sdIsString } from '../../serialization/json'; +import { sdIsList } from '../../serialization/json'; +import { sdIsMap } from '../../serialization/json'; +export type NotesConvertRequestBodyV2026R0ContentFormatField = + | 'markdown' + | string; +export class NotesConvertRequestBodyV2026R0 { + /** + * The content to convert to a note. See the `content_format` field for supported formats. */ + readonly content!: string; + /** + * Format of the content to convert. */ + readonly contentFormat: NotesConvertRequestBodyV2026R0ContentFormatField = + 'markdown' as NotesConvertRequestBodyV2026R0ContentFormatField; + readonly parent!: FolderReferenceV2026R0; + /** + * The name for the created note. The `.boxnote` extension is appended automatically. */ + readonly name!: string; + readonly rawData?: SerializedData; + constructor( + fields: Omit & + Partial>, + ) { + if (fields.content !== undefined) { + this.content = fields.content; + } + if (fields.contentFormat !== undefined) { + this.contentFormat = fields.contentFormat; + } + if (fields.parent !== undefined) { + this.parent = fields.parent; + } + if (fields.name !== undefined) { + this.name = fields.name; + } + if (fields.rawData !== undefined) { + this.rawData = fields.rawData; + } + } +} +export interface NotesConvertRequestBodyV2026R0Input { + /** + * The content to convert to a note. See the `content_format` field for supported formats. */ + readonly content: string; + /** + * Format of the content to convert. */ + readonly contentFormat?: NotesConvertRequestBodyV2026R0ContentFormatField; + readonly parent: FolderReferenceV2026R0; + /** + * The name for the created note. The `.boxnote` extension is appended automatically. */ + readonly name: string; + readonly rawData?: SerializedData; +} +export function serializeNotesConvertRequestBodyV2026R0ContentFormatField( + val: NotesConvertRequestBodyV2026R0ContentFormatField, +): SerializedData { + return val; +} +export function deserializeNotesConvertRequestBodyV2026R0ContentFormatField( + val: SerializedData, +): NotesConvertRequestBodyV2026R0ContentFormatField { + if (val == 'markdown') { + return val; + } + if (sdIsString(val)) { + return val; + } + throw new BoxSdkError({ + message: + "Can't deserialize NotesConvertRequestBodyV2026R0ContentFormatField", + }); +} +export function serializeNotesConvertRequestBodyV2026R0( + val: NotesConvertRequestBodyV2026R0, +): SerializedData { + return { + ['content']: val.content, + ['content_format']: + serializeNotesConvertRequestBodyV2026R0ContentFormatField( + val.contentFormat, + ), + ['parent']: serializeFolderReferenceV2026R0(val.parent), + ['name']: val.name, + }; +} +export function deserializeNotesConvertRequestBodyV2026R0( + val: SerializedData, +): NotesConvertRequestBodyV2026R0 { + if (!sdIsMap(val)) { + throw new BoxSdkError({ + message: 'Expecting a map for "NotesConvertRequestBodyV2026R0"', + }); + } + if (val.content == void 0) { + throw new BoxSdkError({ + message: + 'Expecting "content" of type "NotesConvertRequestBodyV2026R0" to be defined', + }); + } + if (!sdIsString(val.content)) { + throw new BoxSdkError({ + message: + 'Expecting string for "content" of type "NotesConvertRequestBodyV2026R0"', + }); + } + const content: string = val.content; + if (val.content_format == void 0) { + throw new BoxSdkError({ + message: + 'Expecting "content_format" of type "NotesConvertRequestBodyV2026R0" to be defined', + }); + } + const contentFormat: NotesConvertRequestBodyV2026R0ContentFormatField = + deserializeNotesConvertRequestBodyV2026R0ContentFormatField( + val.content_format, + ); + if (val.parent == void 0) { + throw new BoxSdkError({ + message: + 'Expecting "parent" of type "NotesConvertRequestBodyV2026R0" to be defined', + }); + } + const parent: FolderReferenceV2026R0 = deserializeFolderReferenceV2026R0( + val.parent, + ); + if (val.name == void 0) { + throw new BoxSdkError({ + message: + 'Expecting "name" of type "NotesConvertRequestBodyV2026R0" to be defined', + }); + } + if (!sdIsString(val.name)) { + throw new BoxSdkError({ + message: + 'Expecting string for "name" of type "NotesConvertRequestBodyV2026R0"', + }); + } + const name: string = val.name; + return { + content: content, + contentFormat: contentFormat, + parent: parent, + name: name, + } satisfies NotesConvertRequestBodyV2026R0; +} +export function serializeNotesConvertRequestBodyV2026R0Input( + val: NotesConvertRequestBodyV2026R0Input, +): SerializedData { + return { + ['content']: val.content, + ['contentFormat']: + val.contentFormat == void 0 + ? val.contentFormat + : serializeNotesConvertRequestBodyV2026R0ContentFormatField( + val.contentFormat, + ), + ['parent']: serializeFolderReferenceV2026R0(val.parent), + ['name']: val.name, + }; +} +export function deserializeNotesConvertRequestBodyV2026R0Input( + val: SerializedData, +): NotesConvertRequestBodyV2026R0Input { + if (!sdIsMap(val)) { + throw new BoxSdkError({ + message: 'Expecting a map for "NotesConvertRequestBodyV2026R0Input"', + }); + } + if (val.content == void 0) { + throw new BoxSdkError({ + message: + 'Expecting "content" of type "NotesConvertRequestBodyV2026R0Input" to be defined', + }); + } + if (!sdIsString(val.content)) { + throw new BoxSdkError({ + message: + 'Expecting string for "content" of type "NotesConvertRequestBodyV2026R0Input"', + }); + } + const content: string = val.content; + const contentFormat: + | undefined + | NotesConvertRequestBodyV2026R0ContentFormatField = + val.contentFormat == void 0 + ? void 0 + : deserializeNotesConvertRequestBodyV2026R0ContentFormatField( + val.contentFormat, + ); + if (val.parent == void 0) { + throw new BoxSdkError({ + message: + 'Expecting "parent" of type "NotesConvertRequestBodyV2026R0Input" to be defined', + }); + } + const parent: FolderReferenceV2026R0 = deserializeFolderReferenceV2026R0( + val.parent, + ); + if (val.name == void 0) { + throw new BoxSdkError({ + message: + 'Expecting "name" of type "NotesConvertRequestBodyV2026R0Input" to be defined', + }); + } + if (!sdIsString(val.name)) { + throw new BoxSdkError({ + message: + 'Expecting string for "name" of type "NotesConvertRequestBodyV2026R0Input"', + }); + } + const name: string = val.name; + return { + content: content, + contentFormat: contentFormat, + parent: parent, + name: name, + } satisfies NotesConvertRequestBodyV2026R0Input; +} diff --git a/src/schemas/v2026R0/notesConvertResponseV2026R0.ts b/src/schemas/v2026R0/notesConvertResponseV2026R0.ts new file mode 100644 index 000000000..e779e244d --- /dev/null +++ b/src/schemas/v2026R0/notesConvertResponseV2026R0.ts @@ -0,0 +1,134 @@ +import { BoxSdkError } from '../../box/errors'; +import { SerializedData } from '../../serialization/json'; +import { sdIsEmpty } from '../../serialization/json'; +import { sdIsBoolean } from '../../serialization/json'; +import { sdIsNumber } from '../../serialization/json'; +import { sdIsString } from '../../serialization/json'; +import { sdIsList } from '../../serialization/json'; +import { sdIsMap } from '../../serialization/json'; +export type NotesConvertResponseV2026R0TypeField = 'file'; +export class NotesConvertResponseV2026R0 { + /** + * The Box resource type; always `file` for a Box file. */ + readonly type: NotesConvertResponseV2026R0TypeField = + 'file' as NotesConvertResponseV2026R0TypeField; + /** + * Box file ID of the created `.boxnote` file. */ + readonly id!: string; + readonly rawData?: SerializedData; + constructor( + fields: Omit & + Partial>, + ) { + if (fields.type !== undefined) { + this.type = fields.type; + } + if (fields.id !== undefined) { + this.id = fields.id; + } + if (fields.rawData !== undefined) { + this.rawData = fields.rawData; + } + } +} +export interface NotesConvertResponseV2026R0Input { + /** + * The Box resource type; always `file` for a Box file. */ + readonly type?: NotesConvertResponseV2026R0TypeField; + /** + * Box file ID of the created `.boxnote` file. */ + readonly id: string; + readonly rawData?: SerializedData; +} +export function serializeNotesConvertResponseV2026R0TypeField( + val: NotesConvertResponseV2026R0TypeField, +): SerializedData { + return val; +} +export function deserializeNotesConvertResponseV2026R0TypeField( + val: SerializedData, +): NotesConvertResponseV2026R0TypeField { + if (val == 'file') { + return val; + } + throw new BoxSdkError({ + message: "Can't deserialize NotesConvertResponseV2026R0TypeField", + }); +} +export function serializeNotesConvertResponseV2026R0( + val: NotesConvertResponseV2026R0, +): SerializedData { + return { + ['type']: serializeNotesConvertResponseV2026R0TypeField(val.type), + ['id']: val.id, + }; +} +export function deserializeNotesConvertResponseV2026R0( + val: SerializedData, +): NotesConvertResponseV2026R0 { + if (!sdIsMap(val)) { + throw new BoxSdkError({ + message: 'Expecting a map for "NotesConvertResponseV2026R0"', + }); + } + if (val.type == void 0) { + throw new BoxSdkError({ + message: + 'Expecting "type" of type "NotesConvertResponseV2026R0" to be defined', + }); + } + const type: NotesConvertResponseV2026R0TypeField = + deserializeNotesConvertResponseV2026R0TypeField(val.type); + if (val.id == void 0) { + throw new BoxSdkError({ + message: + 'Expecting "id" of type "NotesConvertResponseV2026R0" to be defined', + }); + } + if (!sdIsString(val.id)) { + throw new BoxSdkError({ + message: + 'Expecting string for "id" of type "NotesConvertResponseV2026R0"', + }); + } + const id: string = val.id; + return { type: type, id: id } satisfies NotesConvertResponseV2026R0; +} +export function serializeNotesConvertResponseV2026R0Input( + val: NotesConvertResponseV2026R0Input, +): SerializedData { + return { + ['type']: + val.type == void 0 + ? val.type + : serializeNotesConvertResponseV2026R0TypeField(val.type), + ['id']: val.id, + }; +} +export function deserializeNotesConvertResponseV2026R0Input( + val: SerializedData, +): NotesConvertResponseV2026R0Input { + if (!sdIsMap(val)) { + throw new BoxSdkError({ + message: 'Expecting a map for "NotesConvertResponseV2026R0Input"', + }); + } + const type: undefined | NotesConvertResponseV2026R0TypeField = + val.type == void 0 + ? void 0 + : deserializeNotesConvertResponseV2026R0TypeField(val.type); + if (val.id == void 0) { + throw new BoxSdkError({ + message: + 'Expecting "id" of type "NotesConvertResponseV2026R0Input" to be defined', + }); + } + if (!sdIsString(val.id)) { + throw new BoxSdkError({ + message: + 'Expecting string for "id" of type "NotesConvertResponseV2026R0Input"', + }); + } + const id: string = val.id; + return { type: type, id: id } satisfies NotesConvertResponseV2026R0Input; +} diff --git a/test-browser/.gitignore b/test-browser/.gitignore index 76c1c0423..6b0409c19 100644 --- a/test-browser/.gitignore +++ b/test-browser/.gitignore @@ -1,24 +1,11 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - # dependencies /node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions # testing /coverage -# next.js -/.next/ -/out/ - # production -/build +/dist # misc .DS_Store @@ -26,21 +13,17 @@ # debug npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* -# env files (can opt-in for committing if needed) +# env files .env* -# vercel -.vercel - # typescript *.tsbuildinfo -next-env.d.ts + +# generated +src/utils/importTests.ts # cypress cypress/screenshots cypress/videos -cypress/downloads \ No newline at end of file +cypress/downloads diff --git a/test-browser/README.md b/test-browser/README.md index 5b0de9c5b..147d544ca 100644 --- a/test-browser/README.md +++ b/test-browser/README.md @@ -1,6 +1,6 @@ # Box TypeScript SDK Gen Browser Test Runner -A browser-based test runner for Box TypeScript SDK, built with Next.js and Cypress. +A browser-based test runner for Box TypeScript SDK, built with Vite and Cypress. ## Features diff --git a/test-browser/eslint.config.mjs b/test-browser/eslint.config.mjs deleted file mode 100644 index 7f86eca7f..000000000 --- a/test-browser/eslint.config.mjs +++ /dev/null @@ -1,16 +0,0 @@ -import { dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { FlatCompat } from '@eslint/eslintrc'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const compat = new FlatCompat({ - baseDirectory: __dirname, -}); - -const eslintConfig = [ - ...compat.extends('next/core-web-vitals', 'next/typescript'), -]; - -export default eslintConfig; diff --git a/test-browser/index.html b/test-browser/index.html new file mode 100644 index 000000000..5d8aa4a84 --- /dev/null +++ b/test-browser/index.html @@ -0,0 +1,30 @@ + + + + + + Browser Test + + + +
+
+

Test Results

+
+ + + + + + + + + + + +
Test NameStatusDurationErrorActions
+
+
+ + + diff --git a/test-browser/next.config.ts b/test-browser/next.config.ts deleted file mode 100644 index fa5c7fc96..000000000 --- a/test-browser/next.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { NextConfig } from 'next'; -import path from 'path'; - -const nextConfig: NextConfig = { - /* config options here */ - outputFileTracingRoot: path.join(__dirname, '../'), -}; - -export default nextConfig; diff --git a/test-browser/package.json b/test-browser/package.json index 277bc8dcf..ec22b1262 100644 --- a/test-browser/package.json +++ b/test-browser/package.json @@ -2,35 +2,28 @@ "name": "box-node-sdk-browser-test", "version": "0.1.0", "private": true, + "type": "module", "scripts": { - "dev": "npm run prebuild && next dev --turbopack", + "dev": "npm run prebuild && vite", "prebuild": "tsx init.ts", - "build": "next build", - "build:dev": "NODE_ENV=development next build", + "build": "npm run prebuild && vite build", + "start": "vite preview", "build-run": "npm run build && npm run start", - "start": "next start", - "lint": "next lint", "cypress:open": "cypress open", "cypress:run": "cypress run", "cypress:run:all": "npm run cypress:run:chrome && npm run cypress:run:firefox", - "cypress:run:chrome": "start-server-and-test build-run 3000 'cypress run --browser chrome'", - "cypress:run:firefox": "start-server-and-test build-run 3000 'cypress run --browser firefox'" + "cypress:run:chrome": "start-server-and-test 'npm run build-run' http://localhost:3000 'cypress run --browser chrome'", + "cypress:run:firefox": "start-server-and-test 'npm run build-run' http://localhost:3000 'cypress run --browser firefox'" }, "dependencies": { - "box-node-sdk": "file:../", - "next": "15.5.18", - "react": "^19.0.0", - "react-dom": "^19.0.0" + "box-node-sdk": "file:../" }, "devDependencies": { "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", "cypress": "^14.5.3", - "eslint": "^9", - "eslint-config-next": "15.4.10", "start-server-and-test": "^2.0.11", "tsx": "^4.20.3", - "typescript": "^5" + "typescript": "^5", + "vite": "^6.0.0" } } diff --git a/test-browser/pages/api/env.ts b/test-browser/pages/api/env.ts deleted file mode 100644 index 7b63d5930..000000000 --- a/test-browser/pages/api/env.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { testEnvList } from '../../sdkTest.config.mjs'; - -export default function handler(req: NextApiRequest, res: NextApiResponse) { - try { - const envObject = Object.fromEntries( - testEnvList.map((env) => [env, process.env[env]]), - ); - res.status(200).json(envObject); - } catch (error) { - res.status(500).json({ - error: error instanceof Error ? error.message : 'Unknown error', - }); - } -} diff --git a/test-browser/src/app/favicon.ico b/test-browser/src/app/favicon.ico deleted file mode 100644 index 718d6fea4..000000000 Binary files a/test-browser/src/app/favicon.ico and /dev/null differ diff --git a/test-browser/src/app/globals.css b/test-browser/src/app/globals.css deleted file mode 100644 index 9925df3fc..000000000 --- a/test-browser/src/app/globals.css +++ /dev/null @@ -1,29 +0,0 @@ -:root { - --background: #ffffff; - --foreground: #171717; -} - -html, -body { - max-width: 100vw; - overflow-x: hidden; -} - -body { - color: var(--foreground); - background: var(--background); - font-family: Arial, Helvetica, sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -* { - box-sizing: border-box; - padding: 0; - margin: 0; -} - -a { - color: inherit; - text-decoration: none; -} diff --git a/test-browser/src/app/layout.tsx b/test-browser/src/app/layout.tsx deleted file mode 100644 index d96094a3c..000000000 --- a/test-browser/src/app/layout.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { Metadata } from 'next'; -import { Geist, Geist_Mono } from 'next/font/google'; -import './globals.css'; - -const geistSans = Geist({ - variable: '--font-geist-sans', - subsets: ['latin'], -}); - -const geistMono = Geist_Mono({ - variable: '--font-geist-mono', - subsets: ['latin'], -}); - -export const metadata: Metadata = { - title: 'Browser Test', -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - - {children} - - - ); -} diff --git a/test-browser/src/app/page.module.css b/test-browser/src/app/page.module.css deleted file mode 100644 index 107990b83..000000000 --- a/test-browser/src/app/page.module.css +++ /dev/null @@ -1,24 +0,0 @@ -.page { - --gray-rgb: 0, 0, 0; - --gray-alpha-200: rgba(var(--gray-rgb), 0.08); - --gray-alpha-100: rgba(var(--gray-rgb), 0.05); - - --button-primary-hover: #383838; - --button-secondary-hover: #f2f2f2; - - display: grid; - grid-template-rows: 20px 1fr 20px; - align-items: center; - justify-items: center; - min-height: 100svh; - padding: 0px 80px; - gap: 64px; - font-family: var(--font-geist-sans); -} - -.main { - display: flex; - flex-direction: column; - gap: 32px; - grid-row-start: 2; -} diff --git a/test-browser/src/app/page.tsx b/test-browser/src/app/page.tsx deleted file mode 100644 index 6a3c27616..000000000 --- a/test-browser/src/app/page.tsx +++ /dev/null @@ -1,154 +0,0 @@ -'use client'; - -import styles from './page.module.css'; -import { useEffect, useState } from 'react'; -import { setEnvVar } from 'box-node-sdk/internal'; -import { setupExpect } from '@/utils/expect'; -import { TestResult, createTestRegistry } from '@/utils/testRegistry'; -import { testConfig } from '@/../sdkTest.config.mjs'; - -declare global { - interface Window { - testRegistry: ReturnType; - test: (name: string, fn: () => Promise) => void; - } -} - -export default function Home() { - const [testResults, setTestResults] = useState([]); - const [error, setError] = useState(); - - const registerTestMethod = () => { - window.testRegistry = createTestRegistry(setTestResults, setError); - // @ts-expect-error - Ignore Mocha TestFunction type conflict - window.test = async (name: string, fn: () => Promise) => { - window.testRegistry.register(name, fn); - if (testConfig[name as keyof typeof testConfig] === 'skip') { - console.log(`Skipping test: ${name}`); - setTestResults((prev) => - prev.map((r) => (r.name === name ? { ...r, status: 'skipped' } : r)), - ); - } - }; - }; - - const setupEnvironment = async () => { - await fetch('/api/env') - .then((res) => res.json()) - .then((data) => { - for (const [key, value] of Object.entries(data)) { - setEnvVar(key, value as string); - } - }); - }; - - useEffect(() => { - registerTestMethod(); - setupExpect(); - setupEnvironment().then(async () => { - try { - await import('../utils/importTests'); - setTimeout(() => { - window.testRegistry.runAll(); - }, 3000); - } catch (e) { - setError( - e instanceof Error ? e.message : 'Failed to import test files', - ); - } - }); - }, []); - - // Add new useEffect to update test results label - useEffect(() => { - const passed = testResults.filter((r) => r.status === 'passed').length; - const failed = testResults.filter((r) => r.status === 'failed').length; - const running = testResults.filter((r) => r.status === 'running').length; - const skipped = testResults.filter((r) => r.status === 'skipped').length; - const total = testResults.length; - setError( - `Test Results: ${passed}/${total} passed, ${running} running, ${failed} failed, ${skipped} skipped`, - ); - }, [testResults]); - - const getStatusColor = (status: TestResult['status']) => { - switch (status) { - case 'passed': - return 'green'; - case 'failed': - return 'red'; - case 'running': - return 'orange'; - case 'skipped': - return 'gray'; - default: - return 'gray'; - } - }; - - const formatDuration = (startTime?: Date, endTime?: Date) => { - if (!startTime || !endTime) return '-'; - const duration = endTime.getTime() - startTime.getTime(); - return `${duration}ms`; - }; - - return ( -
-
-

Test Results

- {error && ( -
- Error: {error} -
- )} - - - - - - - - - - - - {testResults.map((result, index) => ( - - - - - - - - ))} - -
Test NameStatusDurationErrorActions
{result.name} - {result.status.toUpperCase()} - - {formatDuration(result.startTime, result.endTime)} - {result.error} - -
-
-
- ); -} diff --git a/test-browser/src/main.ts b/test-browser/src/main.ts new file mode 100644 index 000000000..0ad265ff2 --- /dev/null +++ b/test-browser/src/main.ts @@ -0,0 +1,91 @@ +import { setEnvVar } from 'box-node-sdk/internal'; +import { setupExpect } from './utils/expect'; +import { createTestRegistry } from './utils/testRegistry'; +import { testConfig } from '../sdkTest.config.mjs'; + +declare global { + interface Window { + testRegistry: ReturnType; + test: (name: string, fn: () => Promise) => void; + } +} + +const statusEl = document.getElementById('status')!; +const tbodyEl = document.getElementById('test-results')!; + +function updateStatus() { + const rows = tbodyEl.querySelectorAll('tr'); + let passed = 0, + failed = 0, + running = 0, + skipped = 0; + rows.forEach((row) => { + const status = row.dataset.status; + if (status === 'passed') passed++; + else if (status === 'failed') failed++; + else if (status === 'running') running++; + else if (status === 'skipped') skipped++; + }); + const total = rows.length; + statusEl.textContent = `Test Results: ${passed}/${total} passed, ${running} running, ${failed} failed, ${skipped} skipped`; +} + +function renderRow( + name: string, + status: string, + duration: string, + error: string, +) { + let row = tbodyEl.querySelector( + `tr[data-test="${CSS.escape(name)}"]`, + ) as HTMLTableRowElement | null; + if (!row) { + row = document.createElement('tr'); + row.dataset.test = name; + tbodyEl.appendChild(row); + } + row.dataset.status = status; + row.innerHTML = ` + ${name} + ${status.toUpperCase()} + ${duration} + ${error} + + `; + row.querySelector('button')!.addEventListener('click', () => { + window.testRegistry.runByName(name); + }); + updateStatus(); +} + +window.testRegistry = createTestRegistry(renderRow); + +window.test = (name: string, fn: () => Promise) => { + window.testRegistry.register(name, fn); + if (testConfig[name as keyof typeof testConfig] === 'skip') { + console.log(`Skipping test: ${name}`); + renderRow(name, 'skipped', '-', ''); + } +}; + +setupExpect(); + +async function init() { + const res = await fetch('/api/env'); + const data = await res.json(); + for (const [key, value] of Object.entries(data)) { + setEnvVar(key, value as string); + } + + try { + await import('./utils/importTests'); + setTimeout(() => { + window.testRegistry.runAll(); + }, 3000); + } catch (e) { + statusEl.textContent = + e instanceof Error ? e.message : 'Failed to import test files'; + } +} + +init(); diff --git a/test-browser/src/shims/node-fetch.ts b/test-browser/src/shims/node-fetch.ts new file mode 100644 index 000000000..b3deda51a --- /dev/null +++ b/test-browser/src/shims/node-fetch.ts @@ -0,0 +1,4 @@ +export default fetch; +export const Headers = globalThis.Headers; +export const Request = globalThis.Request; +export const Response = globalThis.Response; diff --git a/test-browser/src/styles.css b/test-browser/src/styles.css new file mode 100644 index 000000000..7350dc518 --- /dev/null +++ b/test-browser/src/styles.css @@ -0,0 +1,100 @@ +:root { + --background: #ffffff; + --foreground: #171717; +} + +html, +body { + max-width: 100vw; + overflow-x: hidden; +} + +body { + color: var(--foreground); + background: var(--background); + font-family: Arial, Helvetica, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +#app { + display: grid; + grid-template-rows: 20px 1fr 20px; + align-items: center; + justify-items: center; + min-height: 100svh; + padding: 0 80px; + gap: 64px; +} + +main { + display: flex; + flex-direction: column; + gap: 32px; + grid-row-start: 2; + width: 100%; +} + +table { + width: 100%; + border-collapse: collapse; +} + +th { + text-align: left; + padding: 8px; +} + +td { + padding: 8px; +} + +tr + tr { + border-top: 1px solid #ddd; +} + +#status { + color: red; + margin-bottom: 1rem; +} + +.status-passed { + color: green; +} +.status-failed { + color: red; +} +.status-running { + color: orange; +} +.status-skipped { + color: gray; +} +.status-not_started { + color: gray; +} + +.error-cell { + color: red; +} + +button { + padding: 4px 8px; + background-color: #0070f3; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} diff --git a/test-browser/src/utils/testRegistry.ts b/test-browser/src/utils/testRegistry.ts index 466059d20..948569cf4 100644 --- a/test-browser/src/utils/testRegistry.ts +++ b/test-browser/src/utils/testRegistry.ts @@ -8,6 +8,13 @@ export type TestResult = { endTime?: Date; }; +export type RenderFn = ( + name: string, + status: string, + duration: string, + error: string, +) => void; + export type TestRegistry = { tests: Array<{ name: string; fn: () => Promise }>; register: (name: string, fn: () => Promise) => void; @@ -15,16 +22,21 @@ export type TestRegistry = { runByName: (name: string) => Promise; }; -export function createTestRegistry( - setTestResults: React.Dispatch>, - setError: React.Dispatch>, -): TestRegistry { +function formatDuration(startTime?: Date, endTime?: Date): string { + if (!startTime || !endTime) return '-'; + return `${endTime.getTime() - startTime.getTime()}ms`; +} + +export function createTestRegistry(render: RenderFn): TestRegistry { + const results = new Map(); + return { tests: [] as Array<{ name: string; fn: () => Promise }>, register(name: string, fn: () => Promise) { this.tests.push({ name, fn }); - setTestResults((prev) => [...prev, { name, status: 'not_started' }]); + results.set(name, { name, status: 'not_started' }); + render(name, 'not_started', '-', ''); console.log(`Registered test: ${name}`); }, @@ -33,99 +45,66 @@ export function createTestRegistry( `Running ${this.tests.length} tests with 10 concurrent tests...`, ); - try { - const concurrencyLimit = 10; - const runningTests = new Set(); - const testQueue = [...this.tests]; + const concurrencyLimit = 10; + const runningTests = new Set(); + const testQueue = [...this.tests]; - // Function to run next test if we have capacity - const runNextTest = async () => { - if (runningTests.size >= concurrencyLimit || testQueue.length === 0) { - return; - } + const runNextTest = async () => { + if (runningTests.size >= concurrencyLimit || testQueue.length === 0) { + return; + } - const test = testQueue.shift()!; - runningTests.add(test.name); + const test = testQueue.shift()!; + runningTests.add(test.name); - try { - await this.runByName(test.name); - } finally { - runningTests.delete(test.name); - // Try to run another test if we have capacity - await runNextTest(); - } - }; + try { + await this.runByName(test.name); + } finally { + runningTests.delete(test.name); + await runNextTest(); + } + }; - // Start initial tests up to concurrency limit - const initialTests = Math.min(concurrencyLimit, testQueue.length); - await Promise.all( - Array(initialTests) - .fill(null) - .map(() => runNextTest()), - ); - } catch (e) { - console.error('Error running tests:', e); - setError( - 'Error running tests: ' + - (e instanceof Error ? e.message : 'Unknown error'), - ); - } + const initialTests = Math.min(concurrencyLimit, testQueue.length); + await Promise.all( + Array(initialTests) + .fill(null) + .map(() => runNextTest()), + ); }, async runByName(name: string) { - const test = this.tests.find( - (t: { name: string; fn: () => Promise }) => t.name === name, - ); + const test = this.tests.find((t) => t.name === name); if (!test) { console.error(`Test not found: ${name}`); return; } if (testConfig[name as keyof typeof testConfig] === 'skip') { console.log(`Skipping test: ${name}`); - setTestResults((prev) => - prev.map((r) => - r.name === test.name ? { ...r, status: 'skipped' } : r, - ), - ); + results.set(name, { name, status: 'skipped' }); + render(name, 'skipped', '-', ''); return; } try { - setTestResults((prev) => - prev.map((r) => - r.name === test.name - ? { - ...r, - status: 'running', - startTime: new Date(), - endTime: undefined, - error: undefined, - } - : r, - ), - ); + const startTime = new Date(); + results.set(name, { name, status: 'running', startTime }); + render(name, 'running', '-', ''); console.log(`Running test: ${test.name}`); await test.fn(); - setTestResults((prev) => - prev.map((r) => - r.name === test.name - ? { - ...r, - status: 'passed', - endTime: new Date(), - error: undefined, - } - : r, - ), - ); + const endTime = new Date(); + results.set(name, { name, status: 'passed', startTime, endTime }); + render(name, 'passed', formatDuration(startTime, endTime), ''); console.log(`✓ Test passed: ${test.name}`); } catch (e) { const error = e instanceof Error ? e.message : 'Unknown error'; - setTestResults((prev) => - prev.map((r) => - r.name === test.name - ? { ...r, status: 'failed', error, endTime: new Date() } - : r, - ), + const result = results.get(name)!; + const endTime = new Date(); + results.set(name, { ...result, status: 'failed', error, endTime }); + render( + name, + 'failed', + formatDuration(result.startTime, endTime), + error, ); console.error(`✗ Test failed: ${test.name}`, e); } diff --git a/test-browser/tsconfig.json b/test-browser/tsconfig.json index cd69de88f..84be03955 100644 --- a/test-browser/tsconfig.json +++ b/test-browser/tsconfig.json @@ -11,23 +11,10 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], "paths": { "@/*": ["./src/*"] } }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts", - "sdkTest.config.mjs" - ], - "exclude": ["node_modules", ".next"] + "include": ["**/*.ts", "sdkTest.config.mjs"], + "exclude": ["node_modules", "dist"] } diff --git a/test-browser/vite.config.ts b/test-browser/vite.config.ts new file mode 100644 index 000000000..c8390c31c --- /dev/null +++ b/test-browser/vite.config.ts @@ -0,0 +1,56 @@ +import { defineConfig } from 'vite'; +import path from 'path'; +import { testEnvList } from './sdkTest.config.mjs'; + +export default defineConfig({ + define: { + global: 'globalThis', + }, + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + 'node-fetch': path.resolve(__dirname, 'src/shims/node-fetch.ts'), + }, + preserveSymlinks: true, + }, + optimizeDeps: { + include: ['box-node-sdk'], + }, + build: { + commonjsOptions: { + include: [/box-node-sdk/, /node_modules/], + transformMixedEsModules: true, + }, + }, + server: { + port: 3000, + strictPort: true, + }, + preview: { + port: 3000, + strictPort: true, + }, + plugins: [ + { + name: 'env-api', + configureServer(server) { + server.middlewares.use('/api/env', (_req, res) => { + const envObject = Object.fromEntries( + testEnvList.map((env: string) => [env, process.env[env]]), + ); + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(envObject)); + }); + }, + configurePreviewServer(server) { + server.middlewares.use('/api/env', (_req, res) => { + const envObject = Object.fromEntries( + testEnvList.map((env: string) => [env, process.env[env]]), + ); + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(envObject)); + }); + }, + }, + ], +});