@storyblok/management-api-client 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/client.cjs +196 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +761 -0
- package/dist/client.d.mts +761 -0
- package/dist/client.mjs +195 -0
- package/dist/client.mjs.map +1 -0
- package/dist/error.cjs.map +1 -1
- package/dist/error.d.cts +12 -2
- package/dist/error.d.mts +12 -2
- package/dist/error.mjs.map +1 -1
- package/dist/generated/mapi/_internal.gen.d.cts +371 -0
- package/dist/generated/mapi/_internal.gen.d.mts +371 -0
- package/dist/generated/{shared → mapi}/client/client.gen.cjs +1 -1
- package/dist/generated/mapi/client/client.gen.cjs.map +1 -0
- package/dist/generated/{shared → mapi}/client/client.gen.mjs +1 -1
- package/dist/generated/mapi/client/client.gen.mjs.map +1 -0
- package/dist/generated/{shared → mapi}/client/types.gen.d.cts +1 -1
- package/dist/generated/{shared → mapi}/client/types.gen.d.mts +1 -1
- package/dist/generated/{shared → mapi}/client/utils.gen.cjs +1 -1
- package/dist/generated/mapi/client/utils.gen.cjs.map +1 -0
- package/dist/generated/{shared → mapi}/client/utils.gen.d.cts +1 -1
- package/dist/generated/{shared → mapi}/client/utils.gen.d.mts +1 -1
- package/dist/generated/{shared → mapi}/client/utils.gen.mjs +1 -1
- package/dist/generated/mapi/client/utils.gen.mjs.map +1 -0
- package/dist/generated/mapi/client.gen.cjs +10 -0
- package/dist/generated/mapi/client.gen.cjs.map +1 -0
- package/dist/generated/mapi/client.gen.mjs +10 -0
- package/dist/generated/mapi/client.gen.mjs.map +1 -0
- package/dist/generated/{shared → mapi}/core/auth.gen.cjs +1 -1
- package/dist/generated/mapi/core/auth.gen.cjs.map +1 -0
- package/dist/generated/{shared → mapi}/core/auth.gen.d.cts +1 -1
- package/dist/generated/{shared → mapi}/core/auth.gen.d.mts +1 -1
- package/dist/generated/{shared → mapi}/core/auth.gen.mjs +1 -1
- package/dist/generated/mapi/core/auth.gen.mjs.map +1 -0
- package/dist/generated/{shared → mapi}/core/bodySerializer.gen.cjs +1 -1
- package/dist/generated/mapi/core/bodySerializer.gen.cjs.map +1 -0
- package/dist/generated/{shared → mapi}/core/bodySerializer.gen.d.cts +1 -1
- package/dist/generated/{shared → mapi}/core/bodySerializer.gen.d.mts +1 -1
- package/dist/generated/{shared → mapi}/core/bodySerializer.gen.mjs +1 -1
- package/dist/generated/mapi/core/bodySerializer.gen.mjs.map +1 -0
- package/dist/generated/{shared → mapi}/core/params.gen.cjs +1 -1
- package/dist/generated/mapi/core/params.gen.cjs.map +1 -0
- package/dist/generated/{shared → mapi}/core/params.gen.mjs +1 -1
- package/dist/generated/mapi/core/params.gen.mjs.map +1 -0
- package/dist/generated/{shared → mapi}/core/pathSerializer.gen.cjs +1 -1
- package/dist/generated/mapi/core/pathSerializer.gen.cjs.map +1 -0
- package/dist/generated/{shared → mapi}/core/pathSerializer.gen.d.cts +1 -1
- package/dist/generated/{shared → mapi}/core/pathSerializer.gen.d.mts +1 -1
- package/dist/generated/{shared → mapi}/core/pathSerializer.gen.mjs +1 -1
- package/dist/generated/mapi/core/pathSerializer.gen.mjs.map +1 -0
- package/dist/generated/{shared → mapi}/core/serverSentEvents.gen.cjs +1 -1
- package/dist/generated/mapi/core/serverSentEvents.gen.cjs.map +1 -0
- package/dist/generated/{shared → mapi}/core/serverSentEvents.gen.d.cts +1 -1
- package/dist/generated/{shared → mapi}/core/serverSentEvents.gen.d.mts +1 -1
- package/dist/generated/{shared → mapi}/core/serverSentEvents.gen.mjs +1 -1
- package/dist/generated/mapi/core/serverSentEvents.gen.mjs.map +1 -0
- package/dist/generated/{shared → mapi}/core/types.gen.d.cts +1 -1
- package/dist/generated/{shared → mapi}/core/types.gen.d.mts +1 -1
- package/dist/generated/{shared → mapi}/core/utils.gen.cjs +1 -1
- package/dist/generated/mapi/core/utils.gen.cjs.map +1 -0
- package/dist/generated/{shared → mapi}/core/utils.gen.mjs +1 -1
- package/dist/generated/mapi/core/utils.gen.mjs.map +1 -0
- package/dist/generated/mapi/sdk.gen.cjs +1477 -0
- package/dist/generated/mapi/sdk.gen.cjs.map +1 -0
- package/dist/generated/mapi/sdk.gen.mjs +1399 -0
- package/dist/generated/mapi/sdk.gen.mjs.map +1 -0
- package/dist/generated/mapi/types-aliased.gen.d.cts +1598 -0
- package/dist/generated/mapi/types-aliased.gen.d.mts +1598 -0
- package/dist/generated/mapi/types.gen.d.cts +5075 -0
- package/dist/generated/mapi/types.gen.d.mts +5075 -0
- package/dist/generated/overlay/_internal.gen.d.cts +850 -0
- package/dist/generated/overlay/_internal.gen.d.mts +850 -0
- package/dist/generated/types/_utils.d.cts +7 -0
- package/dist/generated/types/_utils.d.mts +7 -0
- package/dist/generated/types/block.d.cts +55 -0
- package/dist/generated/types/block.d.mts +55 -0
- package/dist/generated/types/field.d.cts +122 -0
- package/dist/generated/types/field.d.mts +122 -0
- package/dist/generated/types/mapi-story.d.cts +29 -0
- package/dist/generated/types/mapi-story.d.mts +29 -0
- package/dist/index.cjs +3 -181
- package/dist/index.d.cts +12 -650
- package/dist/index.d.mts +12 -649
- package/dist/index.mjs +2 -179
- package/dist/resources/asset-folders.cjs +9 -9
- package/dist/resources/asset-folders.cjs.map +1 -1
- package/dist/resources/asset-folders.mjs +9 -9
- package/dist/resources/asset-folders.mjs.map +1 -1
- package/dist/resources/assets.cjs +57 -50
- package/dist/resources/assets.cjs.map +1 -1
- package/dist/resources/assets.d.cts +23 -27
- package/dist/resources/assets.d.mts +23 -27
- package/dist/resources/assets.mjs +57 -50
- package/dist/resources/assets.mjs.map +1 -1
- package/dist/resources/component-folders.cjs +9 -9
- package/dist/resources/component-folders.cjs.map +1 -1
- package/dist/resources/component-folders.mjs +9 -9
- package/dist/resources/component-folders.mjs.map +1 -1
- package/dist/resources/components.cjs +28 -20
- package/dist/resources/components.cjs.map +1 -1
- package/dist/resources/components.d.cts +76 -0
- package/dist/resources/components.d.mts +76 -0
- package/dist/resources/components.mjs +28 -20
- package/dist/resources/components.mjs.map +1 -1
- package/dist/resources/datasource-entries.cjs +28 -34
- package/dist/resources/datasource-entries.cjs.map +1 -1
- package/dist/resources/datasource-entries.mjs +29 -35
- package/dist/resources/datasource-entries.mjs.map +1 -1
- package/dist/resources/datasources.cjs +27 -9
- package/dist/resources/datasources.cjs.map +1 -1
- package/dist/resources/datasources.mjs +27 -9
- package/dist/resources/datasources.mjs.map +1 -1
- package/dist/resources/experiments.cjs +31 -31
- package/dist/resources/experiments.cjs.map +1 -1
- package/dist/resources/experiments.mjs +31 -31
- package/dist/resources/experiments.mjs.map +1 -1
- package/dist/resources/internal-tags.cjs +7 -7
- package/dist/resources/internal-tags.cjs.map +1 -1
- package/dist/resources/internal-tags.mjs +7 -7
- package/dist/resources/internal-tags.mjs.map +1 -1
- package/dist/resources/presets.cjs +9 -9
- package/dist/resources/presets.cjs.map +1 -1
- package/dist/resources/presets.mjs +9 -9
- package/dist/resources/presets.mjs.map +1 -1
- package/dist/resources/shared-asset-folders.cjs.map +1 -1
- package/dist/resources/shared-asset-folders.d.cts +1 -1
- package/dist/resources/shared-asset-folders.d.mts +1 -1
- package/dist/resources/shared-asset-folders.mjs.map +1 -1
- package/dist/resources/shared-assets.cjs.map +1 -1
- package/dist/resources/shared-assets.d.cts +2 -2
- package/dist/resources/shared-assets.d.mts +2 -2
- package/dist/resources/shared-assets.mjs.map +1 -1
- package/dist/resources/shared-internal-tags.cjs.map +1 -1
- package/dist/resources/shared-internal-tags.mjs.map +1 -1
- package/dist/resources/shared.cjs +15 -0
- package/dist/resources/shared.cjs.map +1 -1
- package/dist/resources/shared.mjs +15 -1
- package/dist/resources/shared.mjs.map +1 -1
- package/dist/resources/spaces.cjs +8 -7
- package/dist/resources/spaces.cjs.map +1 -1
- package/dist/resources/spaces.d.cts +8 -0
- package/dist/resources/spaces.d.mts +7 -0
- package/dist/resources/spaces.mjs +8 -7
- package/dist/resources/spaces.mjs.map +1 -1
- package/dist/resources/stories.cjs +20 -16
- package/dist/resources/stories.cjs.map +1 -1
- package/dist/resources/stories.d.cts +93 -0
- package/dist/resources/stories.d.mts +93 -0
- package/dist/resources/stories.mjs +20 -16
- package/dist/resources/stories.mjs.map +1 -1
- package/dist/resources/users.cjs +3 -3
- package/dist/resources/users.cjs.map +1 -1
- package/dist/resources/users.mjs +3 -3
- package/dist/resources/users.mjs.map +1 -1
- package/dist/utils/query-serializer.cjs +54 -0
- package/dist/utils/query-serializer.cjs.map +1 -0
- package/dist/utils/query-serializer.mjs +54 -0
- package/dist/utils/query-serializer.mjs.map +1 -0
- package/dist/utils/rate-limit.cjs +3 -7
- package/dist/utils/rate-limit.cjs.map +1 -1
- package/dist/utils/rate-limit.mjs +3 -6
- package/dist/utils/rate-limit.mjs.map +1 -1
- package/dist/utils/throttle.cjs +57 -0
- package/dist/utils/throttle.cjs.map +1 -0
- package/dist/utils/throttle.mjs +56 -0
- package/dist/utils/throttle.mjs.map +1 -0
- package/package.json +17 -16
- package/playground/integration-tests/README.md +24 -0
- package/playground/integration-tests/eslint.config.js +5 -0
- package/playground/integration-tests/node_modules/.bin/eslint +16 -0
- package/playground/integration-tests/node_modules/.bin/tsc +16 -0
- package/playground/integration-tests/node_modules/.bin/tsserver +16 -0
- package/playground/integration-tests/node_modules/.bin/vitest +16 -0
- package/playground/integration-tests/package.json +24 -0
- package/playground/integration-tests/test/setup.e2e.ts +11 -0
- package/playground/integration-tests/test/specs/mapi-round-trip.spec.e2e.ts +510 -0
- package/playground/integration-tests/test/types/components.test-d.ts +74 -0
- package/playground/integration-tests/test/types/resources.test-d.ts +212 -0
- package/playground/integration-tests/test/types/stories.test-d.ts +310 -0
- package/playground/integration-tests/vitest.config.e2e.ts +25 -0
- package/playground/integration-tests/vitest.config.ts +13 -0
- package/test/GUIDE.md +59 -0
- package/vitest.config.ts +5 -0
- package/dist/generated/asset_folders/client.gen.cjs +0 -10
- package/dist/generated/asset_folders/client.gen.cjs.map +0 -1
- package/dist/generated/asset_folders/client.gen.mjs +0 -10
- package/dist/generated/asset_folders/client.gen.mjs.map +0 -1
- package/dist/generated/asset_folders/sdk.gen.cjs +0 -99
- package/dist/generated/asset_folders/sdk.gen.cjs.map +0 -1
- package/dist/generated/asset_folders/sdk.gen.mjs +0 -95
- package/dist/generated/asset_folders/sdk.gen.mjs.map +0 -1
- package/dist/generated/asset_folders/types.gen.d.cts +0 -156
- package/dist/generated/asset_folders/types.gen.d.mts +0 -156
- package/dist/generated/assets/client.gen.cjs +0 -10
- package/dist/generated/assets/client.gen.cjs.map +0 -1
- package/dist/generated/assets/client.gen.mjs +0 -10
- package/dist/generated/assets/client.gen.mjs.map +0 -1
- package/dist/generated/assets/sdk.gen.cjs +0 -179
- package/dist/generated/assets/sdk.gen.cjs.map +0 -1
- package/dist/generated/assets/sdk.gen.mjs +0 -171
- package/dist/generated/assets/sdk.gen.mjs.map +0 -1
- package/dist/generated/assets/types.gen.d.cts +0 -415
- package/dist/generated/assets/types.gen.d.mts +0 -415
- package/dist/generated/component_folders/client.gen.cjs +0 -10
- package/dist/generated/component_folders/client.gen.cjs.map +0 -1
- package/dist/generated/component_folders/client.gen.mjs +0 -10
- package/dist/generated/component_folders/client.gen.mjs.map +0 -1
- package/dist/generated/component_folders/sdk.gen.cjs +0 -99
- package/dist/generated/component_folders/sdk.gen.cjs.map +0 -1
- package/dist/generated/component_folders/sdk.gen.mjs +0 -95
- package/dist/generated/component_folders/sdk.gen.mjs.map +0 -1
- package/dist/generated/component_folders/types.gen.d.cts +0 -118
- package/dist/generated/component_folders/types.gen.d.mts +0 -118
- package/dist/generated/components/client.gen.cjs +0 -10
- package/dist/generated/components/client.gen.cjs.map +0 -1
- package/dist/generated/components/client.gen.mjs +0 -10
- package/dist/generated/components/client.gen.mjs.map +0 -1
- package/dist/generated/components/sdk.gen.cjs +0 -171
- package/dist/generated/components/sdk.gen.cjs.map +0 -1
- package/dist/generated/components/sdk.gen.mjs +0 -163
- package/dist/generated/components/sdk.gen.mjs.map +0 -1
- package/dist/generated/components/types.gen.d.cts +0 -855
- package/dist/generated/components/types.gen.d.mts +0 -855
- package/dist/generated/datasource_entries/client.gen.cjs +0 -10
- package/dist/generated/datasource_entries/client.gen.cjs.map +0 -1
- package/dist/generated/datasource_entries/client.gen.mjs +0 -10
- package/dist/generated/datasource_entries/client.gen.mjs.map +0 -1
- package/dist/generated/datasource_entries/sdk.gen.cjs +0 -89
- package/dist/generated/datasource_entries/sdk.gen.cjs.map +0 -1
- package/dist/generated/datasource_entries/sdk.gen.mjs +0 -85
- package/dist/generated/datasource_entries/sdk.gen.mjs.map +0 -1
- package/dist/generated/datasource_entries/types.gen.d.cts +0 -156
- package/dist/generated/datasource_entries/types.gen.d.mts +0 -156
- package/dist/generated/datasources/client.gen.cjs +0 -10
- package/dist/generated/datasources/client.gen.cjs.map +0 -1
- package/dist/generated/datasources/client.gen.mjs +0 -10
- package/dist/generated/datasources/client.gen.mjs.map +0 -1
- package/dist/generated/datasources/sdk.gen.cjs +0 -89
- package/dist/generated/datasources/sdk.gen.cjs.map +0 -1
- package/dist/generated/datasources/sdk.gen.mjs +0 -85
- package/dist/generated/datasources/sdk.gen.mjs.map +0 -1
- package/dist/generated/datasources/types.gen.d.cts +0 -186
- package/dist/generated/datasources/types.gen.d.mts +0 -186
- package/dist/generated/experiments/client.gen.cjs +0 -10
- package/dist/generated/experiments/client.gen.cjs.map +0 -1
- package/dist/generated/experiments/client.gen.mjs +0 -10
- package/dist/generated/experiments/client.gen.mjs.map +0 -1
- package/dist/generated/experiments/sdk.gen.cjs +0 -298
- package/dist/generated/experiments/sdk.gen.cjs.map +0 -1
- package/dist/generated/experiments/sdk.gen.mjs +0 -283
- package/dist/generated/experiments/sdk.gen.mjs.map +0 -1
- package/dist/generated/experiments/types.gen.d.cts +0 -522
- package/dist/generated/experiments/types.gen.d.mts +0 -522
- package/dist/generated/internal_tags/client.gen.cjs +0 -10
- package/dist/generated/internal_tags/client.gen.cjs.map +0 -1
- package/dist/generated/internal_tags/client.gen.mjs +0 -10
- package/dist/generated/internal_tags/client.gen.mjs.map +0 -1
- package/dist/generated/internal_tags/sdk.gen.cjs +0 -74
- package/dist/generated/internal_tags/sdk.gen.cjs.map +0 -1
- package/dist/generated/internal_tags/sdk.gen.mjs +0 -71
- package/dist/generated/internal_tags/sdk.gen.mjs.map +0 -1
- package/dist/generated/internal_tags/types.gen.d.cts +0 -106
- package/dist/generated/internal_tags/types.gen.d.mts +0 -106
- package/dist/generated/presets/client.gen.cjs +0 -10
- package/dist/generated/presets/client.gen.cjs.map +0 -1
- package/dist/generated/presets/client.gen.mjs +0 -10
- package/dist/generated/presets/client.gen.mjs.map +0 -1
- package/dist/generated/presets/sdk.gen.cjs +0 -99
- package/dist/generated/presets/sdk.gen.cjs.map +0 -1
- package/dist/generated/presets/sdk.gen.mjs +0 -95
- package/dist/generated/presets/sdk.gen.mjs.map +0 -1
- package/dist/generated/presets/types.gen.d.cts +0 -176
- package/dist/generated/presets/types.gen.d.mts +0 -176
- package/dist/generated/shared/client/client.gen.cjs.map +0 -1
- package/dist/generated/shared/client/client.gen.mjs.map +0 -1
- package/dist/generated/shared/client/utils.gen.cjs.map +0 -1
- package/dist/generated/shared/client/utils.gen.mjs.map +0 -1
- package/dist/generated/shared/core/auth.gen.cjs.map +0 -1
- package/dist/generated/shared/core/auth.gen.mjs.map +0 -1
- package/dist/generated/shared/core/bodySerializer.gen.cjs.map +0 -1
- package/dist/generated/shared/core/bodySerializer.gen.mjs.map +0 -1
- package/dist/generated/shared/core/params.gen.cjs.map +0 -1
- package/dist/generated/shared/core/params.gen.mjs.map +0 -1
- package/dist/generated/shared/core/pathSerializer.gen.cjs.map +0 -1
- package/dist/generated/shared/core/pathSerializer.gen.mjs.map +0 -1
- package/dist/generated/shared/core/serverSentEvents.gen.cjs.map +0 -1
- package/dist/generated/shared/core/serverSentEvents.gen.mjs.map +0 -1
- package/dist/generated/shared/core/utils.gen.cjs.map +0 -1
- package/dist/generated/shared/core/utils.gen.mjs.map +0 -1
- package/dist/generated/spaces/client.gen.cjs +0 -10
- package/dist/generated/spaces/client.gen.cjs.map +0 -1
- package/dist/generated/spaces/client.gen.mjs +0 -10
- package/dist/generated/spaces/client.gen.mjs.map +0 -1
- package/dist/generated/spaces/sdk.gen.cjs +0 -99
- package/dist/generated/spaces/sdk.gen.cjs.map +0 -1
- package/dist/generated/spaces/sdk.gen.mjs +0 -95
- package/dist/generated/spaces/sdk.gen.mjs.map +0 -1
- package/dist/generated/spaces/types.gen.d.cts +0 -544
- package/dist/generated/spaces/types.gen.d.mts +0 -544
- package/dist/generated/stories/client.gen.cjs +0 -10
- package/dist/generated/stories/client.gen.cjs.map +0 -1
- package/dist/generated/stories/client.gen.mjs +0 -10
- package/dist/generated/stories/client.gen.mjs.map +0 -1
- package/dist/generated/stories/sdk.gen.cjs +0 -138
- package/dist/generated/stories/sdk.gen.cjs.map +0 -1
- package/dist/generated/stories/sdk.gen.mjs +0 -131
- package/dist/generated/stories/sdk.gen.mjs.map +0 -1
- package/dist/generated/stories/types.gen.d.cts +0 -1306
- package/dist/generated/stories/types.gen.d.mts +0 -1306
- package/dist/generated/users/client.gen.cjs +0 -10
- package/dist/generated/users/client.gen.cjs.map +0 -1
- package/dist/generated/users/client.gen.mjs +0 -10
- package/dist/generated/users/client.gen.mjs.map +0 -1
- package/dist/generated/users/sdk.gen.cjs +0 -44
- package/dist/generated/users/sdk.gen.cjs.map +0 -1
- package/dist/generated/users/sdk.gen.mjs +0 -43
- package/dist/generated/users/sdk.gen.mjs.map +0 -1
- package/dist/generated/users/types.gen.d.cts +0 -348
- package/dist/generated/users/types.gen.d.mts +0 -348
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/types.d.cts +0 -130
- package/dist/types.d.mts +0 -130
- /package/dist/generated/{shared → mapi}/client/client.gen.d.mts +0 -0
- /package/dist/generated/{shared → mapi}/client/index.cjs +0 -0
- /package/dist/generated/{shared → mapi}/client/index.d.mts +0 -0
- /package/dist/generated/{shared → mapi}/client/index.mjs +0 -0
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end test: @storyblok/schema define functions → MAPI round-trip
|
|
3
|
+
*
|
|
4
|
+
* Validates that:
|
|
5
|
+
* 1. Every major MAPI define function produces a valid creation payload.
|
|
6
|
+
* 2. The MAPI accepts those payloads and creates real resources.
|
|
7
|
+
* 3. The types returned by the schema-aware mapi-client match the actual
|
|
8
|
+
* runtime data returned by the API (no type lies).
|
|
9
|
+
* 4. Zod schemas generated from the OpenAPI spec accept real API responses.
|
|
10
|
+
*
|
|
11
|
+
* Run manually (never in CI):
|
|
12
|
+
* pnpm --filter @storyblok/mapi-integration-tests test:e2e
|
|
13
|
+
*
|
|
14
|
+
* Requires .env.qa-engineer-manual at the repo root with:
|
|
15
|
+
* STORYBLOK_TOKEN=<personal-access-token>
|
|
16
|
+
* STORYBLOK_SPACE_ID=<numeric-space-id>
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
20
|
+
import { createManagementApiClient } from '@storyblok/management-api-client';
|
|
21
|
+
import type { StoryCreate, StoryUpdate } from '@storyblok/management-api-client';
|
|
22
|
+
import {
|
|
23
|
+
defineBlock,
|
|
24
|
+
defineField,
|
|
25
|
+
} from '@storyblok/schema';
|
|
26
|
+
|
|
27
|
+
const token = process.env.STORYBLOK_TOKEN!;
|
|
28
|
+
const spaceId = Number(process.env.STORYBLOK_SPACE_ID!);
|
|
29
|
+
|
|
30
|
+
const PREFIX = 'e2e_schema_';
|
|
31
|
+
const STORY_SLUG_PREFIX = 'e2e-schema-';
|
|
32
|
+
|
|
33
|
+
const DATASOURCE_SLUG = 'e2e-schema-categories';
|
|
34
|
+
const DATASOURCE_NAME = `${PREFIX}categories`;
|
|
35
|
+
const STORY_NAME = 'E2E Schema Test Page';
|
|
36
|
+
const STORY_SLUG = `${STORY_SLUG_PREFIX}test-page`;
|
|
37
|
+
|
|
38
|
+
const teaserComponent = defineBlock({
|
|
39
|
+
name: `${PREFIX}teaser`,
|
|
40
|
+
fields: [
|
|
41
|
+
defineField('title', { type: 'text', required: true }),
|
|
42
|
+
defineField('image', { type: 'asset' }),
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
// Level-2 container: holds teasers in its `items` bloks field (level 3)
|
|
46
|
+
const sectionComponent = defineBlock({
|
|
47
|
+
name: `${PREFIX}section`,
|
|
48
|
+
fields: [
|
|
49
|
+
defineField('title', { type: 'text' }),
|
|
50
|
+
defineField('items', { type: 'bloks', allow: [teaserComponent.name], required: true }),
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
const pageComponent = defineBlock({
|
|
54
|
+
name: `${PREFIX}page`,
|
|
55
|
+
is_root: true,
|
|
56
|
+
fields: [
|
|
57
|
+
defineField('headline', { type: 'text', required: true }),
|
|
58
|
+
defineField('rating', { type: 'number' }),
|
|
59
|
+
defineField('is_featured', { type: 'boolean' }),
|
|
60
|
+
defineField('description', { type: 'richtext' }),
|
|
61
|
+
defineField('body', { type: 'bloks', allow: [teaserComponent.name, sectionComponent.name], required: true }),
|
|
62
|
+
defineField('category', { type: 'option', source: 'internal', datasource: DATASOURCE_SLUG }),
|
|
63
|
+
defineField('any_blocks', { type: 'bloks', required: true }),
|
|
64
|
+
],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
interface StoryblokTypes {
|
|
68
|
+
components: typeof pageComponent | typeof teaserComponent | typeof sectionComponent;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type Blocks = StoryblokTypes['components'];
|
|
72
|
+
|
|
73
|
+
const client = createManagementApiClient({
|
|
74
|
+
personalAccessToken: token,
|
|
75
|
+
spaceId,
|
|
76
|
+
throwOnError: true,
|
|
77
|
+
}).withTypes<StoryblokTypes>();
|
|
78
|
+
|
|
79
|
+
async function cleanup() {
|
|
80
|
+
// Stories
|
|
81
|
+
const storiesRes = await client.stories.list({ query: { per_page: 100 } });
|
|
82
|
+
for (const story of storiesRes.data?.stories ?? []) {
|
|
83
|
+
if (story.slug?.startsWith(STORY_SLUG_PREFIX) && story.id) {
|
|
84
|
+
await client.stories.delete(story.id);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Presets
|
|
89
|
+
const presetsRes = await client.presets.list();
|
|
90
|
+
for (const preset of presetsRes.data?.presets ?? []) {
|
|
91
|
+
if (preset.name?.startsWith(PREFIX) && preset.id) {
|
|
92
|
+
await client.presets.delete(preset.id);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Internal tags
|
|
97
|
+
const tagsRes = await client.internalTags.list();
|
|
98
|
+
for (const tag of tagsRes.data?.internal_tags ?? []) {
|
|
99
|
+
if (tag.name?.startsWith(PREFIX) && tag.id) {
|
|
100
|
+
await client.internalTags.delete(tag.id);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Components
|
|
105
|
+
const compsRes = await client.components.list();
|
|
106
|
+
for (const comp of compsRes.data?.components ?? []) {
|
|
107
|
+
if (comp.name?.startsWith(PREFIX) && comp.id) {
|
|
108
|
+
await client.components.delete(comp.id);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Component folders
|
|
113
|
+
const foldersRes = await client.componentFolders.list();
|
|
114
|
+
for (const folder of foldersRes.data?.component_groups ?? []) {
|
|
115
|
+
if (folder.name?.startsWith(PREFIX) && folder.id) {
|
|
116
|
+
await client.componentFolders.delete(folder.id);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Datasource entries, then datasources
|
|
121
|
+
const dsRes = await client.datasources.list();
|
|
122
|
+
for (const ds of dsRes.data?.datasources ?? []) {
|
|
123
|
+
if (ds.name?.startsWith(PREFIX) && ds.id) {
|
|
124
|
+
const entriesRes = await client.datasourceEntries.list({
|
|
125
|
+
query: { datasource_id: ds.id, per_page: 100 },
|
|
126
|
+
});
|
|
127
|
+
for (const entry of entriesRes.data?.datasource_entries ?? []) {
|
|
128
|
+
if (entry.id) {
|
|
129
|
+
await client.datasourceEntries.delete(entry.id);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
await client.datasources.delete(ds.id);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
describe('schema + mapi-client MAPI round-trip', () => {
|
|
138
|
+
let pageComponentId: number;
|
|
139
|
+
let teaserComponentId: number;
|
|
140
|
+
let sectionComponentId: number;
|
|
141
|
+
let componentFolderId: number;
|
|
142
|
+
let datasourceId: number;
|
|
143
|
+
let internalTagId: number;
|
|
144
|
+
let presetId: number;
|
|
145
|
+
let storyId: number;
|
|
146
|
+
|
|
147
|
+
beforeAll(async () => {
|
|
148
|
+
await cleanup();
|
|
149
|
+
|
|
150
|
+
// 1. Datasource + entries first — the page component schema references the datasource slug,
|
|
151
|
+
// and the MAPI validates that the datasource exists at component creation time.
|
|
152
|
+
// Create payloads are plain MAPI wire objects validated by the typed client.
|
|
153
|
+
const dsRes = await client.datasources.create({ body: { datasource: {
|
|
154
|
+
name: DATASOURCE_NAME,
|
|
155
|
+
slug: DATASOURCE_SLUG,
|
|
156
|
+
} } });
|
|
157
|
+
datasourceId = dsRes.data!.datasource!.id!;
|
|
158
|
+
|
|
159
|
+
for (const entry of [
|
|
160
|
+
{ name: 'Technology', value: 'tech', datasource_id: datasourceId },
|
|
161
|
+
{ name: 'Design', value: 'design', datasource_id: datasourceId },
|
|
162
|
+
{ name: 'Business', value: 'business', datasource_id: datasourceId },
|
|
163
|
+
]) {
|
|
164
|
+
await client.datasourceEntries.create({ body: { datasource_entry: entry } });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 2. Component folder
|
|
168
|
+
const folderRes = await client.componentFolders.create({ body: { component_group: { name: `${PREFIX}folder` } } });
|
|
169
|
+
componentFolderId = folderRes.data!.component_group!.id!;
|
|
170
|
+
const folderUuid = folderRes.data!.component_group!.uuid;
|
|
171
|
+
|
|
172
|
+
// 3. Teaser component (innermost — whitelisted by section)
|
|
173
|
+
const teaserRes = await client.components.create({ body: { component: {
|
|
174
|
+
name: teaserComponent.name,
|
|
175
|
+
schema: {
|
|
176
|
+
title: { type: 'text', required: true, pos: 0 },
|
|
177
|
+
image: { type: 'asset', pos: 1 },
|
|
178
|
+
},
|
|
179
|
+
component_group_uuid: folderUuid,
|
|
180
|
+
} } });
|
|
181
|
+
teaserComponentId = teaserRes.data!.component!.id!;
|
|
182
|
+
|
|
183
|
+
// 4. Section component (level 2 — whitelists teaser, whitelisted by page)
|
|
184
|
+
const sectionRes = await client.components.create({ body: { component: {
|
|
185
|
+
name: sectionComponent.name,
|
|
186
|
+
schema: {
|
|
187
|
+
title: { type: 'text', pos: 0 },
|
|
188
|
+
items: { type: 'bloks', component_whitelist: [teaserComponent.name], pos: 1 },
|
|
189
|
+
},
|
|
190
|
+
component_group_uuid: folderUuid,
|
|
191
|
+
} } });
|
|
192
|
+
sectionComponentId = sectionRes.data!.component!.id!;
|
|
193
|
+
|
|
194
|
+
// 5. Page component (level 1 — whitelists both teaser and section in body)
|
|
195
|
+
const pageRes = await client.components.create({ body: { component: {
|
|
196
|
+
name: pageComponent.name,
|
|
197
|
+
schema: {
|
|
198
|
+
headline: { type: 'text', required: true, pos: 0 },
|
|
199
|
+
rating: { type: 'number', pos: 1 },
|
|
200
|
+
is_featured: { type: 'boolean', pos: 2 },
|
|
201
|
+
description: { type: 'richtext', pos: 3 },
|
|
202
|
+
body: { type: 'bloks', component_whitelist: [teaserComponent.name, sectionComponent.name], pos: 4 },
|
|
203
|
+
category: { type: 'option', source: 'internal', datasource_slug: DATASOURCE_SLUG, pos: 5 },
|
|
204
|
+
any_blocks: { type: 'bloks', pos: 6 },
|
|
205
|
+
},
|
|
206
|
+
component_group_uuid: folderUuid,
|
|
207
|
+
is_root: true,
|
|
208
|
+
} } });
|
|
209
|
+
pageComponentId = pageRes.data!.component!.id!;
|
|
210
|
+
|
|
211
|
+
// 5. Internal tag
|
|
212
|
+
const tagRes = await client.internalTags.create({ body: { internal_tag: { name: `${PREFIX}tag`, object_type: 'component' } } });
|
|
213
|
+
internalTagId = tagRes.data!.internal_tag!.id!;
|
|
214
|
+
|
|
215
|
+
// 6. Preset for page component
|
|
216
|
+
const presetRes = await client.presets.create({ body: { preset: {
|
|
217
|
+
name: `${PREFIX}default_page`,
|
|
218
|
+
component_id: pageComponentId,
|
|
219
|
+
preset: { headline: 'Default Headline', rating: 0, is_featured: false },
|
|
220
|
+
description: `Default preset for ${pageComponent.name}`,
|
|
221
|
+
} } });
|
|
222
|
+
presetId = presetRes.data!.preset!.id!;
|
|
223
|
+
|
|
224
|
+
// 8. Story: body[0]=teaser (level 2), body[1]=section{items:[teaser]} (levels 2+3)
|
|
225
|
+
const storyPayload: StoryCreate<Blocks> = {
|
|
226
|
+
name: STORY_NAME,
|
|
227
|
+
slug: STORY_SLUG,
|
|
228
|
+
content: {
|
|
229
|
+
component: pageComponent.name,
|
|
230
|
+
headline: 'Hello from e2e',
|
|
231
|
+
rating: 42,
|
|
232
|
+
is_featured: true,
|
|
233
|
+
description: {
|
|
234
|
+
type: 'doc',
|
|
235
|
+
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Rich text body.' }] }],
|
|
236
|
+
},
|
|
237
|
+
body: [
|
|
238
|
+
{
|
|
239
|
+
component: teaserComponent.name,
|
|
240
|
+
title: 'Teaser Title',
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
component: sectionComponent.name,
|
|
244
|
+
title: 'Section Title',
|
|
245
|
+
items: [
|
|
246
|
+
{
|
|
247
|
+
component: teaserComponent.name,
|
|
248
|
+
title: 'Nested Teaser Title',
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
category: 'tech',
|
|
254
|
+
any_blocks: [
|
|
255
|
+
{
|
|
256
|
+
component: teaserComponent.name,
|
|
257
|
+
title: 'Any Block Teaser',
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
const storyRes = await client.stories.create({ body: { story: storyPayload } });
|
|
263
|
+
storyId = storyRes.data!.story!.id!;
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
afterAll(cleanup);
|
|
267
|
+
|
|
268
|
+
describe('components', () => {
|
|
269
|
+
it('should create page component with the correct name and schema keys', async () => {
|
|
270
|
+
const res = await client.components.get(pageComponentId);
|
|
271
|
+
const comp = res.data?.component;
|
|
272
|
+
|
|
273
|
+
expect(comp).toBeDefined();
|
|
274
|
+
expect(comp?.name).toBe(pageComponent.name);
|
|
275
|
+
expect(comp?.schema).toMatchObject({
|
|
276
|
+
headline: expect.objectContaining({ type: 'text' }),
|
|
277
|
+
rating: expect.objectContaining({ type: 'number' }),
|
|
278
|
+
is_featured: expect.objectContaining({ type: 'boolean' }),
|
|
279
|
+
description: expect.objectContaining({ type: 'richtext' }),
|
|
280
|
+
body: expect.objectContaining({ type: 'bloks' }),
|
|
281
|
+
category: expect.objectContaining({ type: 'option' }),
|
|
282
|
+
});
|
|
283
|
+
expect(comp?.name).toBe(pageComponent.name);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('should create teaser component with the correct schema', async () => {
|
|
287
|
+
const res = await client.components.get(teaserComponentId);
|
|
288
|
+
const comp = res.data?.component;
|
|
289
|
+
|
|
290
|
+
expect(comp?.name).toBe(teaserComponent.name);
|
|
291
|
+
expect(comp?.schema).toMatchObject({
|
|
292
|
+
title: expect.objectContaining({ type: 'text', required: true }),
|
|
293
|
+
image: expect.objectContaining({ type: 'asset' }),
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should create section component with the correct schema', async () => {
|
|
298
|
+
const res = await client.components.get(sectionComponentId);
|
|
299
|
+
const comp = res.data?.component;
|
|
300
|
+
|
|
301
|
+
expect(comp?.name).toBe(sectionComponent.name);
|
|
302
|
+
expect(comp?.schema).toMatchObject({
|
|
303
|
+
title: expect.objectContaining({ type: 'text' }),
|
|
304
|
+
items: expect.objectContaining({ type: 'bloks', component_whitelist: [teaserComponent.name] }),
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should assign components to the correct component folder', async () => {
|
|
309
|
+
const folderRes = await client.componentFolders.get(componentFolderId);
|
|
310
|
+
const folder = folderRes.data?.component_group;
|
|
311
|
+
|
|
312
|
+
const pageRes = await client.components.get(pageComponentId);
|
|
313
|
+
const comp = pageRes.data?.component;
|
|
314
|
+
|
|
315
|
+
expect(folder?.name).toBe(`${PREFIX}folder`);
|
|
316
|
+
expect(comp?.component_group_uuid).toBe(folder?.uuid);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe('datasources', () => {
|
|
321
|
+
it('should create datasource with the correct name and slug', async () => {
|
|
322
|
+
const res = await client.datasources.list();
|
|
323
|
+
const ds = res.data?.datasources?.find(d => d.id === datasourceId);
|
|
324
|
+
|
|
325
|
+
expect(ds).toBeDefined();
|
|
326
|
+
expect(ds?.name).toBe(DATASOURCE_NAME);
|
|
327
|
+
expect(ds?.slug).toBe(DATASOURCE_SLUG);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('should create datasource entries with the correct names and values', async () => {
|
|
331
|
+
const res = await client.datasourceEntries.list({
|
|
332
|
+
query: { datasource_id: datasourceId, per_page: 100 },
|
|
333
|
+
});
|
|
334
|
+
const entries = res.data?.datasource_entries ?? [];
|
|
335
|
+
|
|
336
|
+
expect(entries.length).toBeGreaterThanOrEqual(3);
|
|
337
|
+
expect(entries.find(e => e.value === 'tech')?.name).toBe('Technology');
|
|
338
|
+
expect(entries.find(e => e.value === 'design')?.name).toBe('Design');
|
|
339
|
+
expect(entries.find(e => e.value === 'business')?.name).toBe('Business');
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
describe('internalTags', () => {
|
|
344
|
+
it('should create and retrieve internal tag', async () => {
|
|
345
|
+
const res = await client.internalTags.list();
|
|
346
|
+
const tag = res.data?.internal_tags?.find(t => t.id === internalTagId);
|
|
347
|
+
|
|
348
|
+
expect(tag).toBeDefined();
|
|
349
|
+
expect(tag?.name).toBe(`${PREFIX}tag`);
|
|
350
|
+
expect(tag?.object_type).toBe('component');
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
describe('presets', () => {
|
|
355
|
+
it('should create preset with the correct component_id and default values', async () => {
|
|
356
|
+
const res = await client.presets.get(presetId);
|
|
357
|
+
const preset = res.data?.preset;
|
|
358
|
+
|
|
359
|
+
expect(preset).toBeDefined();
|
|
360
|
+
expect(preset?.name).toBe(`${PREFIX}default_page`);
|
|
361
|
+
expect(preset?.component_id).toBe(pageComponentId);
|
|
362
|
+
expect(preset?.preset).toMatchObject({
|
|
363
|
+
headline: 'Default Headline',
|
|
364
|
+
rating: 0,
|
|
365
|
+
is_featured: false,
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
describe('stories', () => {
|
|
371
|
+
it('should match defined schema field types at runtime for story content', async () => {
|
|
372
|
+
const res = await client.stories.get(storyId);
|
|
373
|
+
const story = res.data?.story;
|
|
374
|
+
|
|
375
|
+
expect(story).toBeDefined();
|
|
376
|
+
expect(story?.name).toBe(STORY_NAME);
|
|
377
|
+
expect(story?.slug).toBe(STORY_SLUG);
|
|
378
|
+
|
|
379
|
+
// TypeScript narrows story.content to the pageComponent branch here.
|
|
380
|
+
// Accessing story.content.headline etc. only compiles when the component guard passes,
|
|
381
|
+
// so this block validates both runtime correctness and compile-time type narrowing.
|
|
382
|
+
if (story?.content?.component === pageComponent.name) {
|
|
383
|
+
expect(typeof story.content.headline).toBe('string');
|
|
384
|
+
expect(story.content.headline).toBe('Hello from e2e');
|
|
385
|
+
|
|
386
|
+
expect(typeof story.content.rating).toBe('number');
|
|
387
|
+
expect(story.content.rating).toBe(42);
|
|
388
|
+
|
|
389
|
+
expect(typeof story.content.is_featured).toBe('boolean');
|
|
390
|
+
expect(story.content.is_featured).toBe(true);
|
|
391
|
+
|
|
392
|
+
expect(story.content.description).toMatchObject({ type: 'doc' });
|
|
393
|
+
|
|
394
|
+
expect(Array.isArray(story.content.body)).toBe(true);
|
|
395
|
+
expect(story.content.body.length).toBeGreaterThan(0);
|
|
396
|
+
|
|
397
|
+
expect(typeof story.content.category).toBe('string');
|
|
398
|
+
expect(story.content.category).toBe('tech');
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
throw new Error(
|
|
402
|
+
`Expected story.content.component to be '${pageComponent.name}', got '${story?.content?.component}'`,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('should have correct structure for nested teaser blok (two levels: page → teaser)', async () => {
|
|
408
|
+
const res = await client.stories.get(storyId);
|
|
409
|
+
const story = res.data?.story;
|
|
410
|
+
|
|
411
|
+
if (story?.content?.component !== pageComponent.name) {
|
|
412
|
+
throw new Error('Unexpected component discriminant');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const teaser = story.content.body[0];
|
|
416
|
+
|
|
417
|
+
expect(teaser).toBeDefined();
|
|
418
|
+
expect(teaser?.component).toBe(teaserComponent.name);
|
|
419
|
+
expect(typeof teaser?.title).toBe('string');
|
|
420
|
+
expect(teaser?.title).toBe('Teaser Title');
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should resolve correct types for three-level nested blok (page → section → teaser)', async () => {
|
|
424
|
+
const res = await client.stories.get(storyId);
|
|
425
|
+
const story = res.data?.story;
|
|
426
|
+
|
|
427
|
+
if (story?.content?.component !== pageComponent.name) {
|
|
428
|
+
throw new Error('Unexpected component discriminant at level 1');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Level 2: narrow body[1] to section
|
|
432
|
+
const section = story.content.body[1];
|
|
433
|
+
if (section?.component === sectionComponent.name) {
|
|
434
|
+
expect(typeof section.title).toBe('string');
|
|
435
|
+
expect(section.title).toBe('Section Title');
|
|
436
|
+
expect(Array.isArray(section.items)).toBe(true);
|
|
437
|
+
expect(section.items.length).toBeGreaterThan(0);
|
|
438
|
+
|
|
439
|
+
// Level 3: section.items[0] is a typed teaser — not `never`
|
|
440
|
+
const nestedTeaser = section.items[0];
|
|
441
|
+
if (nestedTeaser?.component === teaserComponent.name) {
|
|
442
|
+
expect(typeof nestedTeaser.title).toBe('string');
|
|
443
|
+
expect(nestedTeaser.title).toBe('Nested Teaser Title');
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
throw new Error(`Expected items[0].component to be '${teaserComponent.name}', got '${nestedTeaser?.component}'`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
throw new Error(`Expected body[1].component to be '${sectionComponent.name}', got '${section?.component}'`);
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it('should resolve all schema component types for bloks field without whitelist', async () => {
|
|
455
|
+
const res = await client.stories.get(storyId);
|
|
456
|
+
const story = res.data?.story;
|
|
457
|
+
|
|
458
|
+
if (story?.content?.component !== pageComponent.name) {
|
|
459
|
+
throw new Error('Unexpected component discriminant');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// any_blocks has no component_whitelist — TypeScript resolves items to the full
|
|
463
|
+
// schema union (page | teaser | section), not `never` and not a generic block
|
|
464
|
+
// with only component: string. Accessing blok.title after narrowing to the
|
|
465
|
+
// teaser discriminant proves the discriminated union is correctly typed.
|
|
466
|
+
const blok = story.content.any_blocks[0];
|
|
467
|
+
if (blok?.component === teaserComponent.name) {
|
|
468
|
+
expect(typeof blok.title).toBe('string');
|
|
469
|
+
expect(blok.title).toBe('Any Block Teaser');
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
throw new Error(`Expected any_blocks[0].component to be '${teaserComponent.name}', got '${blok?.component}'`);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('should round-trip story update correctly', async () => {
|
|
477
|
+
const updatedPayload: StoryUpdate<Blocks> = {
|
|
478
|
+
name: `${STORY_NAME} (Updated)`,
|
|
479
|
+
slug: STORY_SLUG,
|
|
480
|
+
content: {
|
|
481
|
+
component: pageComponent.name,
|
|
482
|
+
headline: 'Updated headline',
|
|
483
|
+
rating: 100,
|
|
484
|
+
is_featured: false,
|
|
485
|
+
description: {
|
|
486
|
+
type: 'doc',
|
|
487
|
+
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Updated.' }] }],
|
|
488
|
+
},
|
|
489
|
+
body: [],
|
|
490
|
+
category: 'design',
|
|
491
|
+
any_blocks: [],
|
|
492
|
+
},
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
await client.stories.update(storyId, { body: { story: updatedPayload } });
|
|
496
|
+
|
|
497
|
+
const res = await client.stories.get(storyId);
|
|
498
|
+
const story = res.data?.story;
|
|
499
|
+
|
|
500
|
+
if (story?.content?.component !== pageComponent.name) {
|
|
501
|
+
throw new Error('Unexpected component discriminant after update');
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
expect(story.content.headline).toBe('Updated headline');
|
|
505
|
+
expect(story.content.rating).toBe(100);
|
|
506
|
+
expect(story.content.is_featured).toBe(false);
|
|
507
|
+
expect(story.content.category).toBe('design');
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { defineBlock, defineField } from '@storyblok/schema';
|
|
2
|
+
import { type Component as ComponentMapi, createManagementApiClient } from '@storyblok/management-api-client';
|
|
3
|
+
import { describe, expectTypeOf, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
// Nestable block — not a root story type
|
|
6
|
+
const teaserComponent = defineBlock({
|
|
7
|
+
name: 'teaser',
|
|
8
|
+
fields: [
|
|
9
|
+
defineField('text', { type: 'text' }),
|
|
10
|
+
defineField('image', { type: 'asset' }),
|
|
11
|
+
],
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Root content type, not nestable
|
|
15
|
+
const _pageComponent = defineBlock({
|
|
16
|
+
name: 'page',
|
|
17
|
+
is_root: true,
|
|
18
|
+
is_nestable: false,
|
|
19
|
+
fields: [
|
|
20
|
+
defineField('headline', { type: 'text', required: true }),
|
|
21
|
+
defineField('body', { type: 'richtext' }),
|
|
22
|
+
defineField('teasers', { type: 'bloks', allow: [teaserComponent.name] }),
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const CLIENT_CONFIG = { personalAccessToken: 'test-token', spaceId: 12345 };
|
|
27
|
+
|
|
28
|
+
describe('components.get response shape', () => {
|
|
29
|
+
it('should return the wire Component (a `schema` record) from components.get', async () => {
|
|
30
|
+
const client = createManagementApiClient(CLIENT_CONFIG);
|
|
31
|
+
const result = await client.components.get(123);
|
|
32
|
+
|
|
33
|
+
if (result.data?.component) {
|
|
34
|
+
// components.get() returns the wire-shaped Component definition (a `schema`
|
|
35
|
+
// record), not the DSL `fields` block.
|
|
36
|
+
expectTypeOf(result.data.component.id).toEqualTypeOf<ComponentMapi['id']>();
|
|
37
|
+
expectTypeOf(result.data.component.name).toEqualTypeOf<ComponentMapi['name']>();
|
|
38
|
+
expectTypeOf(result.data.component).toHaveProperty('schema');
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('defineBlock result used in .withTypes() interface', () => {
|
|
44
|
+
interface StoryblokTypes {
|
|
45
|
+
components: typeof _pageComponent | typeof teaserComponent;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
it('should narrow story content to page or teaser after withTypes', async () => {
|
|
49
|
+
const client = createManagementApiClient(CLIENT_CONFIG).withTypes<StoryblokTypes>();
|
|
50
|
+
const result = await client.stories.get(123);
|
|
51
|
+
|
|
52
|
+
if (result.data?.story) {
|
|
53
|
+
// Only page is a root component (is_root: true); teaser is nestable-only
|
|
54
|
+
expectTypeOf(result.data.story.content.component).toEqualTypeOf<'page'>();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should narrow bloks field on page to whitelisted teasers', async () => {
|
|
59
|
+
const client = createManagementApiClient(CLIENT_CONFIG).withTypes<StoryblokTypes>();
|
|
60
|
+
const result = await client.stories.get(123);
|
|
61
|
+
|
|
62
|
+
if (result.data?.story) {
|
|
63
|
+
const story = result.data.story;
|
|
64
|
+
if (story.content.component === 'page') {
|
|
65
|
+
if (story.content.teasers) {
|
|
66
|
+
for (const teaser of story.content.teasers) {
|
|
67
|
+
// teasers whitelists only the teaser component
|
|
68
|
+
expectTypeOf(teaser.component).toEqualTypeOf<'teaser'>();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|