@sanity/workbench 0.1.0-alpha.7 → 0.1.0-alpha.9
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/dist/_chunks-es/index.js +6 -1
- package/dist/_chunks-es/index.js.map +1 -1
- package/dist/_internal.js +3 -8
- package/dist/_internal.js.map +1 -1
- package/dist/core.d.ts +582 -24
- package/dist/core.js +207 -105
- package/dist/core.js.map +1 -1
- package/package.json +9 -3
- package/src/_internal/render.ts +4 -11
- package/src/core/applications/application-list.ts +1 -1
- package/src/core/applications/local-application.ts +6 -2
- package/src/core/canvases.ts +1 -1
- package/src/core/index.ts +1 -1
- package/src/core/log/index.ts +12 -0
- package/src/core/media-libraries.ts +1 -1
- package/src/core/user-applications/core-app.ts +66 -10
- package/src/core/user-applications/studios/index.ts +3 -0
- package/src/core/user-applications/studios/schemas.ts +106 -0
- package/src/core/user-applications/{studio.ts → studios/studio.ts} +56 -214
- package/src/core/user-applications/studios/workspace.ts +143 -0
- package/src/core/user-applications/user-application.ts +36 -5
- package/src/_internal/render.test.ts +0 -73
- package/src/core/__tests__/__fixtures__.ts +0 -248
- package/src/core/applications/application-list.test.ts +0 -222
- package/src/core/applications/local-application.test.ts +0 -93
- package/src/core/canvases.test.ts +0 -38
- package/src/core/log/index.test.ts +0 -133
- package/src/core/media-libraries.test.ts +0 -38
- package/src/core/organizations.test.ts +0 -134
- package/src/core/projects.test.ts +0 -248
- package/src/core/shared/urls.test.ts +0 -182
- package/src/core/user-applications/core-app.test.ts +0 -151
- package/src/core/user-applications/studio.test.ts +0 -928
- package/src/core/user-applications/user-application.test.ts +0 -126
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { CANVAS_DATA } from "./__tests__/__fixtures__";
|
|
4
|
-
import { CanvasApplication } from "./canvases";
|
|
5
|
-
|
|
6
|
-
const setup = () => new CanvasApplication(CANVAS_DATA);
|
|
7
|
-
|
|
8
|
-
describe("CanvasResource", () => {
|
|
9
|
-
it("should have a type of canvas", () => {
|
|
10
|
-
const resource = setup();
|
|
11
|
-
expect(resource.type).toBe("canvas");
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("should have a to of /canvas", () => {
|
|
15
|
-
const resource = setup();
|
|
16
|
-
expect(resource.href).toBe("canvas");
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("should have a name of Canvas", () => {
|
|
20
|
-
const resource = setup();
|
|
21
|
-
expect(resource.title).toBe("Canvas");
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("should have an id", () => {
|
|
25
|
-
const resource = setup();
|
|
26
|
-
expect(resource.id).toBe("cac1Na6lwtEI");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("should implement the toProtocolResource method", () => {
|
|
30
|
-
const resource = setup();
|
|
31
|
-
expect(resource.toProtocolResource()).toMatchInlineSnapshot(`
|
|
32
|
-
{
|
|
33
|
-
"id": "cac1Na6lwtEI",
|
|
34
|
-
"type": "canvas",
|
|
35
|
-
}
|
|
36
|
-
`);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type MockInstance,
|
|
3
|
-
afterEach,
|
|
4
|
-
beforeEach,
|
|
5
|
-
describe,
|
|
6
|
-
expect,
|
|
7
|
-
it,
|
|
8
|
-
vi,
|
|
9
|
-
} from "vitest";
|
|
10
|
-
|
|
11
|
-
import { createLogger } from ".";
|
|
12
|
-
|
|
13
|
-
describe("createLogger", () => {
|
|
14
|
-
let error: MockInstance;
|
|
15
|
-
let warn: MockInstance;
|
|
16
|
-
let info: MockInstance;
|
|
17
|
-
let debug: MockInstance;
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
error = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
21
|
-
warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
22
|
-
info = vi.spyOn(console, "info").mockImplementation(() => undefined);
|
|
23
|
-
debug = vi.spyOn(console, "debug").mockImplementation(() => undefined);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
afterEach(() => vi.restoreAllMocks());
|
|
27
|
-
|
|
28
|
-
it("'none': logs nothing", () => {
|
|
29
|
-
const logger = createLogger({ logLevel: "none" });
|
|
30
|
-
|
|
31
|
-
logger.error("e");
|
|
32
|
-
logger.warn("w");
|
|
33
|
-
logger.info("i");
|
|
34
|
-
logger.debug("d");
|
|
35
|
-
|
|
36
|
-
expect(error).not.toHaveBeenCalled();
|
|
37
|
-
expect(warn).not.toHaveBeenCalled();
|
|
38
|
-
expect(info).not.toHaveBeenCalled();
|
|
39
|
-
expect(debug).not.toHaveBeenCalled();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("'error': logs only error", () => {
|
|
43
|
-
const logger = createLogger({ logLevel: "error" });
|
|
44
|
-
|
|
45
|
-
logger.error("e");
|
|
46
|
-
logger.warn("w");
|
|
47
|
-
logger.info("i");
|
|
48
|
-
logger.debug("d");
|
|
49
|
-
|
|
50
|
-
expect(error).toHaveBeenCalledOnce();
|
|
51
|
-
expect(warn).not.toHaveBeenCalled();
|
|
52
|
-
expect(info).not.toHaveBeenCalled();
|
|
53
|
-
expect(debug).not.toHaveBeenCalled();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("'warn': logs error and warn", () => {
|
|
57
|
-
const logger = createLogger({ logLevel: "warn" });
|
|
58
|
-
|
|
59
|
-
logger.error("e");
|
|
60
|
-
logger.warn("w");
|
|
61
|
-
logger.info("i");
|
|
62
|
-
logger.debug("d");
|
|
63
|
-
|
|
64
|
-
expect(error).toHaveBeenCalledOnce();
|
|
65
|
-
expect(warn).toHaveBeenCalledOnce();
|
|
66
|
-
expect(info).not.toHaveBeenCalled();
|
|
67
|
-
expect(debug).not.toHaveBeenCalled();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("'info' (default): logs error, warn, and info", () => {
|
|
71
|
-
const logger = createLogger();
|
|
72
|
-
|
|
73
|
-
logger.error("e");
|
|
74
|
-
logger.warn("w");
|
|
75
|
-
logger.info("i");
|
|
76
|
-
logger.debug("d");
|
|
77
|
-
|
|
78
|
-
expect(error).toHaveBeenCalledOnce();
|
|
79
|
-
expect(warn).toHaveBeenCalledOnce();
|
|
80
|
-
expect(info).toHaveBeenCalledOnce();
|
|
81
|
-
expect(debug).not.toHaveBeenCalled();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("'debug': logs everything", () => {
|
|
85
|
-
const logger = createLogger({ logLevel: "debug" });
|
|
86
|
-
|
|
87
|
-
logger.error("e");
|
|
88
|
-
logger.warn("w");
|
|
89
|
-
logger.info("i");
|
|
90
|
-
logger.debug("d");
|
|
91
|
-
|
|
92
|
-
expect(error).toHaveBeenCalledOnce();
|
|
93
|
-
expect(warn).toHaveBeenCalledOnce();
|
|
94
|
-
expect(info).toHaveBeenCalledOnce();
|
|
95
|
-
expect(debug).toHaveBeenCalledOnce();
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("prepends [namespace] to all messages", () => {
|
|
99
|
-
const logger = createLogger({ namespace: "myns", logLevel: "debug" });
|
|
100
|
-
|
|
101
|
-
logger.error("msg");
|
|
102
|
-
logger.warn("msg");
|
|
103
|
-
logger.info("msg");
|
|
104
|
-
logger.debug("msg");
|
|
105
|
-
|
|
106
|
-
expect(error).toHaveBeenCalledWith("[myns]", "msg");
|
|
107
|
-
expect(warn).toHaveBeenCalledWith("[myns]", "msg");
|
|
108
|
-
expect(info).toHaveBeenCalledWith("[myns]", "msg");
|
|
109
|
-
expect(debug).toHaveBeenCalledWith("[myns]", "msg");
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it("passes context as a third argument when provided", () => {
|
|
113
|
-
const logger = createLogger({ namespace: "ns", logLevel: "debug" });
|
|
114
|
-
const ctx = { requestId: "123" };
|
|
115
|
-
logger.error("msg", ctx);
|
|
116
|
-
expect(error).toHaveBeenCalledWith("[ns]", "msg", ctx);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("merges base context with call-site context", () => {
|
|
120
|
-
const logger = createLogger({
|
|
121
|
-
namespace: "ns",
|
|
122
|
-
context: { service: "workbench" },
|
|
123
|
-
logLevel: "debug",
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
logger.debug("msg", { requestId: "42" });
|
|
127
|
-
|
|
128
|
-
expect(debug).toHaveBeenCalledWith("[ns]", "msg", {
|
|
129
|
-
service: "workbench",
|
|
130
|
-
requestId: "42",
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { MEDIA_LIBRARY_DATA } from "./__tests__/__fixtures__";
|
|
4
|
-
import { MediaLibraryApplication } from "./media-libraries";
|
|
5
|
-
|
|
6
|
-
const setup = () => new MediaLibraryApplication(MEDIA_LIBRARY_DATA);
|
|
7
|
-
|
|
8
|
-
describe("MediaLibraryApplication", () => {
|
|
9
|
-
it("should have a type of media-library", () => {
|
|
10
|
-
const resource = setup();
|
|
11
|
-
expect(resource.type).toBe("media-library");
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("should have a to of /media", () => {
|
|
15
|
-
const resource = setup();
|
|
16
|
-
expect(resource.href).toBe("media");
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("should have a name of Media Library", () => {
|
|
20
|
-
const resource = setup();
|
|
21
|
-
expect(resource.title).toBe("Media Library");
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("should have an id", () => {
|
|
25
|
-
const resource = setup();
|
|
26
|
-
expect(resource.id).toBe("al34RQcBfuD7");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("should implement the toProtocolResource method", () => {
|
|
30
|
-
const resource = setup();
|
|
31
|
-
expect(resource.toProtocolResource()).toMatchInlineSnapshot(`
|
|
32
|
-
{
|
|
33
|
-
"id": "al34RQcBfuD7",
|
|
34
|
-
"type": "media-library",
|
|
35
|
-
}
|
|
36
|
-
`);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { describe, expect, expectTypeOf, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
Organization,
|
|
5
|
-
OrganizationId,
|
|
6
|
-
OrganizationMember,
|
|
7
|
-
} from "./organizations";
|
|
8
|
-
import { brandOrganizationId, parseOrganization } from "./organizations";
|
|
9
|
-
|
|
10
|
-
describe("brandOrganizationId", () => {
|
|
11
|
-
it("brands a valid organization id", () => {
|
|
12
|
-
const id = brandOrganizationId("oABC123");
|
|
13
|
-
expect(id).toBe("oABC123");
|
|
14
|
-
expectTypeOf(id).toEqualTypeOf<OrganizationId>();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("rejects an empty string", () => {
|
|
18
|
-
expect(() => brandOrganizationId("")).toThrow();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("is not assignable from a plain string", () => {
|
|
22
|
-
expectTypeOf<OrganizationId>().branded.toEqualTypeOf<OrganizationId>();
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe("parseOrganization", () => {
|
|
27
|
-
const VALID_ORG = {
|
|
28
|
-
id: "org123",
|
|
29
|
-
name: "Sanity",
|
|
30
|
-
slug: "sanity",
|
|
31
|
-
createdAt: "2024-01-01T00:00:00Z",
|
|
32
|
-
updatedAt: "2024-01-01T00:00:00Z",
|
|
33
|
-
dashboardStatus: "enabled",
|
|
34
|
-
aiFeaturesStatus: "enabled",
|
|
35
|
-
defaultRoleName: "member",
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const VALID_MEMBER = {
|
|
39
|
-
sanityUserId: "user1",
|
|
40
|
-
isCurrentUser: true,
|
|
41
|
-
user: {
|
|
42
|
-
id: "u1",
|
|
43
|
-
displayName: "Josh",
|
|
44
|
-
familyName: "Ellis",
|
|
45
|
-
givenName: "Josh",
|
|
46
|
-
middleName: null,
|
|
47
|
-
imageUrl: null,
|
|
48
|
-
email: "josh@sanity.io",
|
|
49
|
-
loginProvider: "google",
|
|
50
|
-
},
|
|
51
|
-
roles: [{ name: "admin", title: "Admin" }],
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const VALID_ORG_FULL = {
|
|
55
|
-
...VALID_ORG,
|
|
56
|
-
members: [VALID_MEMBER],
|
|
57
|
-
features: ["customLogo"],
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
it("parses a valid organization with members and features by default", () => {
|
|
61
|
-
const org = parseOrganization(VALID_ORG_FULL);
|
|
62
|
-
expect(org.name).toBe("Sanity");
|
|
63
|
-
expect(org.members).toHaveLength(1);
|
|
64
|
-
expect(org.features).toEqual(["customLogo"]);
|
|
65
|
-
expectTypeOf(org).toEqualTypeOf<Organization>();
|
|
66
|
-
expectTypeOf(org.id).toEqualTypeOf<OrganizationId>();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("rejects missing required fields", () => {
|
|
70
|
-
expect(() => parseOrganization({ id: "org123" })).toThrow();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("rejects missing members by default", () => {
|
|
74
|
-
expect(() => parseOrganization(VALID_ORG)).toThrow();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("rejects invalid enum values", () => {
|
|
78
|
-
expect(() =>
|
|
79
|
-
parseOrganization({
|
|
80
|
-
...VALID_ORG_FULL,
|
|
81
|
-
dashboardStatus: "invalid",
|
|
82
|
-
}),
|
|
83
|
-
).toThrow();
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("strips unknown properties", () => {
|
|
87
|
-
const org = parseOrganization({
|
|
88
|
-
...VALID_ORG_FULL,
|
|
89
|
-
unknownField: "should be stripped",
|
|
90
|
-
});
|
|
91
|
-
expect(org).not.toHaveProperty("unknownField");
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("parses without members when excluded", () => {
|
|
95
|
-
const org = parseOrganization(
|
|
96
|
-
{ ...VALID_ORG, features: ["customLogo"] },
|
|
97
|
-
{ includeMembers: false },
|
|
98
|
-
);
|
|
99
|
-
expect(org).not.toHaveProperty("members");
|
|
100
|
-
expect(org.features).toEqual(["customLogo"]);
|
|
101
|
-
expectTypeOf(org).toEqualTypeOf<Organization<false>>();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("parses without features when excluded", () => {
|
|
105
|
-
const org = parseOrganization(
|
|
106
|
-
{ ...VALID_ORG, members: [VALID_MEMBER] },
|
|
107
|
-
{ includeFeatures: false },
|
|
108
|
-
);
|
|
109
|
-
expect(org).not.toHaveProperty("features");
|
|
110
|
-
expect(org.members).toHaveLength(1);
|
|
111
|
-
expectTypeOf(org).toEqualTypeOf<Organization<true, false>>();
|
|
112
|
-
expectTypeOf(org.members).toEqualTypeOf<OrganizationMember[]>();
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("parses base only when both excluded", () => {
|
|
116
|
-
const org = parseOrganization(VALID_ORG, {
|
|
117
|
-
includeMembers: false,
|
|
118
|
-
includeFeatures: false,
|
|
119
|
-
});
|
|
120
|
-
expect(org).not.toHaveProperty("members");
|
|
121
|
-
expect(org).not.toHaveProperty("features");
|
|
122
|
-
expectTypeOf(org).toEqualTypeOf<Organization<false, false>>();
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it("parses with both members and features", () => {
|
|
126
|
-
const org = parseOrganization(VALID_ORG_FULL, {
|
|
127
|
-
includeMembers: true,
|
|
128
|
-
includeFeatures: true,
|
|
129
|
-
});
|
|
130
|
-
expect(org.members).toHaveLength(1);
|
|
131
|
-
expect(org.features).toEqual(["customLogo"]);
|
|
132
|
-
expectTypeOf(org).toEqualTypeOf<Organization<true, true>>();
|
|
133
|
-
});
|
|
134
|
-
});
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
import { describe, expect, expectTypeOf, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { brandOrganizationId } from "./organizations";
|
|
4
|
-
import type { OrganizationId } from "./organizations";
|
|
5
|
-
import type { Project, ProjectId, ProjectMember } from "./projects";
|
|
6
|
-
import { brandProjectId, parseProject } from "./projects";
|
|
7
|
-
|
|
8
|
-
describe("Project type", () => {
|
|
9
|
-
it("includes members and features by default", () => {
|
|
10
|
-
expectTypeOf<Project>().toHaveProperty("members");
|
|
11
|
-
expectTypeOf<Project>().toHaveProperty("features");
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("excludes members when IncludeMembers is false", () => {
|
|
15
|
-
expectTypeOf<Project<false>>().toHaveProperty("features");
|
|
16
|
-
expectTypeOf<
|
|
17
|
-
"members" extends keyof Project<false> ? true : false
|
|
18
|
-
>().toEqualTypeOf<false>();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("excludes features when IncludeFeatures is false", () => {
|
|
22
|
-
expectTypeOf<Project<true, false>>().toHaveProperty("members");
|
|
23
|
-
expectTypeOf<
|
|
24
|
-
"features" extends keyof Project<true, false> ? true : false
|
|
25
|
-
>().toEqualTypeOf<false>();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("excludes both when both are false", () => {
|
|
29
|
-
expectTypeOf<
|
|
30
|
-
"members" extends keyof Project<false, false> ? true : false
|
|
31
|
-
>().toEqualTypeOf<false>();
|
|
32
|
-
expectTypeOf<
|
|
33
|
-
"features" extends keyof Project<false, false> ? true : false
|
|
34
|
-
>().toEqualTypeOf<false>();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("always includes base properties", () => {
|
|
38
|
-
expectTypeOf<Project>().toHaveProperty("id");
|
|
39
|
-
expectTypeOf<Project>().toHaveProperty("displayName");
|
|
40
|
-
expectTypeOf<Project>().toHaveProperty("organizationId");
|
|
41
|
-
|
|
42
|
-
expectTypeOf<Project<false, false>>().toHaveProperty("id");
|
|
43
|
-
expectTypeOf<Project<false, false>>().toHaveProperty("displayName");
|
|
44
|
-
expectTypeOf<Project<false, false>>().toHaveProperty("organizationId");
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("has branded id and organizationId types", () => {
|
|
48
|
-
expectTypeOf<Project["id"]>().toEqualTypeOf<ProjectId>();
|
|
49
|
-
expectTypeOf<Project["organizationId"]>().toEqualTypeOf<OrganizationId>();
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe("brandProjectId", () => {
|
|
54
|
-
it("brands a valid project id", () => {
|
|
55
|
-
const id = brandProjectId("abc123");
|
|
56
|
-
expect(id).toBe("abc123");
|
|
57
|
-
expectTypeOf(id).toEqualTypeOf<ProjectId>();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("rejects an empty string", () => {
|
|
61
|
-
expect(() => brandProjectId("")).toThrow();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("is not assignable from a plain string", () => {
|
|
65
|
-
expectTypeOf<ProjectId>().branded.toEqualTypeOf<ProjectId>();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("is not assignable from OrganizationId", () => {
|
|
69
|
-
expectTypeOf<
|
|
70
|
-
OrganizationId extends ProjectId ? true : false
|
|
71
|
-
>().toEqualTypeOf<false>();
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
describe("parseProject", () => {
|
|
76
|
-
const VALID_PROJECT = {
|
|
77
|
-
id: "proj123",
|
|
78
|
-
displayName: "My Project",
|
|
79
|
-
studioHost: "my-project",
|
|
80
|
-
organizationId: brandOrganizationId("org123"),
|
|
81
|
-
metadata: {
|
|
82
|
-
integration: "cli",
|
|
83
|
-
color: "#ff0000",
|
|
84
|
-
initialTemplate: "blog",
|
|
85
|
-
cliInitializedAt: "2024-01-01T00:00:00Z",
|
|
86
|
-
},
|
|
87
|
-
isBlocked: false,
|
|
88
|
-
isDisabled: false,
|
|
89
|
-
isDisabledByUser: false,
|
|
90
|
-
activityFeedEnabled: true,
|
|
91
|
-
createdAt: "2024-01-01T00:00:00Z",
|
|
92
|
-
updatedAt: "2024-01-01T00:00:00Z",
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const VALID_MEMBER = {
|
|
96
|
-
id: "member1",
|
|
97
|
-
createdAt: "2024-01-01T00:00:00Z",
|
|
98
|
-
updatedAt: "2024-01-01T00:00:00Z",
|
|
99
|
-
isCurrentUser: true,
|
|
100
|
-
isRobot: false,
|
|
101
|
-
roles: [{ name: "admin", title: "Admin", description: "Full access" }],
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const VALID_PROJECT_FULL = {
|
|
105
|
-
...VALID_PROJECT,
|
|
106
|
-
members: [VALID_MEMBER],
|
|
107
|
-
features: ["customDomain"],
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
it("parses a valid project with members and features by default", () => {
|
|
111
|
-
const project = parseProject(VALID_PROJECT_FULL);
|
|
112
|
-
expect(project.displayName).toBe("My Project");
|
|
113
|
-
expect(project.members).toHaveLength(1);
|
|
114
|
-
expect(project.features).toEqual(["customDomain"]);
|
|
115
|
-
expectTypeOf(project).toEqualTypeOf<Project>();
|
|
116
|
-
expectTypeOf(project.id).toEqualTypeOf<ProjectId>();
|
|
117
|
-
expectTypeOf(project.organizationId).toEqualTypeOf<OrganizationId>();
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("rejects missing required fields", () => {
|
|
121
|
-
expect(() => parseProject({ id: "proj123" })).toThrow();
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("rejects missing members by default", () => {
|
|
125
|
-
expect(() => parseProject(VALID_PROJECT)).toThrow();
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("strips unknown properties", () => {
|
|
129
|
-
const project = parseProject({
|
|
130
|
-
...VALID_PROJECT_FULL,
|
|
131
|
-
unknownField: "should be stripped",
|
|
132
|
-
});
|
|
133
|
-
expect(project).not.toHaveProperty("unknownField");
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("parses without members when excluded", () => {
|
|
137
|
-
const project = parseProject(
|
|
138
|
-
{ ...VALID_PROJECT, features: ["customDomain"] },
|
|
139
|
-
{ includeMembers: false },
|
|
140
|
-
);
|
|
141
|
-
expect(project).not.toHaveProperty("members");
|
|
142
|
-
expect(project.features).toEqual(["customDomain"]);
|
|
143
|
-
expectTypeOf(project).toEqualTypeOf<Project<false>>();
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("parses without features when excluded", () => {
|
|
147
|
-
const project = parseProject(
|
|
148
|
-
{ ...VALID_PROJECT, members: [VALID_MEMBER] },
|
|
149
|
-
{ includeFeatures: false },
|
|
150
|
-
);
|
|
151
|
-
expect(project).not.toHaveProperty("features");
|
|
152
|
-
expect(project.members).toHaveLength(1);
|
|
153
|
-
expectTypeOf(project).toEqualTypeOf<Project<true, false>>();
|
|
154
|
-
expectTypeOf(project.members).toEqualTypeOf<ProjectMember[]>();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("parses base only when both excluded", () => {
|
|
158
|
-
const project = parseProject(VALID_PROJECT, {
|
|
159
|
-
includeMembers: false,
|
|
160
|
-
includeFeatures: false,
|
|
161
|
-
});
|
|
162
|
-
expect(project).not.toHaveProperty("members");
|
|
163
|
-
expect(project).not.toHaveProperty("features");
|
|
164
|
-
expectTypeOf(project).toEqualTypeOf<Project<false, false>>();
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("parses with both members and features", () => {
|
|
168
|
-
const project = parseProject(VALID_PROJECT_FULL, {
|
|
169
|
-
includeMembers: true,
|
|
170
|
-
includeFeatures: true,
|
|
171
|
-
});
|
|
172
|
-
expect(project.members).toHaveLength(1);
|
|
173
|
-
expect(project.features).toEqual(["customDomain"]);
|
|
174
|
-
expectTypeOf(project).toEqualTypeOf<Project<true, true>>();
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
describe("metadata fields are optional", () => {
|
|
178
|
-
const BASE = {
|
|
179
|
-
...VALID_PROJECT,
|
|
180
|
-
members: [VALID_MEMBER],
|
|
181
|
-
features: ["customDomain"],
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
it("parses with only the integration field", () => {
|
|
185
|
-
const project = parseProject({
|
|
186
|
-
...BASE,
|
|
187
|
-
metadata: { integration: "cli" },
|
|
188
|
-
});
|
|
189
|
-
expect(project.metadata).toEqual({ integration: "cli" });
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
describe("metadata.integration", () => {
|
|
193
|
-
it('accepts "cli"', () => {
|
|
194
|
-
const project = parseProject({
|
|
195
|
-
...BASE,
|
|
196
|
-
metadata: { integration: "cli" },
|
|
197
|
-
});
|
|
198
|
-
expect(project.metadata.integration).toBe("cli");
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it('accepts "manage"', () => {
|
|
202
|
-
const project = parseProject({
|
|
203
|
-
...BASE,
|
|
204
|
-
metadata: { integration: "manage" },
|
|
205
|
-
});
|
|
206
|
-
expect(project.metadata.integration).toBe("manage");
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it("rejects an unknown value", () => {
|
|
210
|
-
expect(() =>
|
|
211
|
-
parseProject({
|
|
212
|
-
...BASE,
|
|
213
|
-
metadata: { integration: "unknown" },
|
|
214
|
-
}),
|
|
215
|
-
).toThrow();
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it("rejects a missing integration field", () => {
|
|
219
|
-
expect(() =>
|
|
220
|
-
parseProject({ ...BASE, metadata: { color: "#ff0000" } }),
|
|
221
|
-
).toThrow();
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it("parses without metadata.color", () => {
|
|
226
|
-
const { color: _color, ...rest } = BASE.metadata;
|
|
227
|
-
const project = parseProject({ ...BASE, metadata: rest });
|
|
228
|
-
expect(project.metadata).not.toHaveProperty("color");
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it("parses without metadata.externalStudioHost", () => {
|
|
232
|
-
const project = parseProject({ ...BASE, metadata: BASE.metadata });
|
|
233
|
-
expect(project.metadata).not.toHaveProperty("externalStudioHost");
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it("parses without metadata.initialTemplate", () => {
|
|
237
|
-
const { initialTemplate: _initialTemplate, ...rest } = BASE.metadata;
|
|
238
|
-
const project = parseProject({ ...BASE, metadata: rest });
|
|
239
|
-
expect(project.metadata).not.toHaveProperty("initialTemplate");
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it("parses without metadata.cliInitializedAt", () => {
|
|
243
|
-
const { cliInitializedAt: _cliInitializedAt, ...rest } = BASE.metadata;
|
|
244
|
-
const project = parseProject({ ...BASE, metadata: rest });
|
|
245
|
-
expect(project.metadata).not.toHaveProperty("cliInitializedAt");
|
|
246
|
-
});
|
|
247
|
-
});
|
|
248
|
-
});
|