Skip to content

Commit b067d9b

Browse files
author
Kaushik Shetty
authored
Merge pull request #90 from contentstack/develop
v2.0.0
2 parents 0a03e77 + f4a7fe1 commit b067d9b

File tree

100 files changed

+4305
-2838
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+4305
-2838
lines changed

.github/workflows/sast-scan.yml

Lines changed: 0 additions & 11 deletions
This file was deleted.

.github/workflows/secrets-scan.yml

Lines changed: 0 additions & 11 deletions
This file was deleted.

.gitignore

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ storybook-static
128128
tmp/
129129
temp/
130130

131-
131+
# Docs
132+
jsdocs/
132133

133134
.DS_Store
134135

@@ -139,6 +140,3 @@ temp/
139140
!.vscode/launch.json
140141
!.vscode/extensions.json
141142
*.code-workspace
142-
143-
144-
# End of https://www.toptal.com/developers/gitignore/api/node,web,vscode

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ npm install @contentstack/app-sdk
1313
Alternatively, you can use the following command within the script tag to install the App SDK:
1414

1515
```html
16-
<script src="https://unpkg.com/@contentstack/app-sdk@^1.4.1/dist/index.js"></script>
16+
<script src="https://unpkg.com/@contentstack/app-sdk@2.0.0/dist/index.js"></script>
1717
```
1818

1919
### Initializing the App SDK
@@ -46,6 +46,8 @@ The Contentstack App SDK currently supports the following UI Locations:
4646
- [App Config Location](https://www.contentstack.com/docs/developers/developer-hub/app-config-location)
4747
- [RTE Location](https://www.contentstack.com/docs/developers/developer-hub/rte-location)
4848
- [Sidebar Location](https://www.contentstack.com/docs/developers/developer-hub/sidebar-location)
49+
- [Field Modifier Location](https://www.contentstack.com/docs/developers/developer-hub/field-modifier-location/)
50+
- [Full Page Location](https://www.contentstack.com/docs/developers/developer-hub/full-page-location)
4951

5052
### Custom Field Location
5153

@@ -73,6 +75,14 @@ The RTE Location allows you to create custom plugins to expand the functionality
7375

7476
The Sidebar Location provides powerful tools for analyzing and recommending ideas for your entry. Use the [Smartling](https://help.smartling.com/hc/en-us/articles/4865477629083) sidebar location to help translate your content.
7577

78+
### Field Modifier Location
79+
80+
The Field Modifier Location is a type of UI location which extends the capabilities of entry fields. With the Field Modifier UI location, you can allow the different apps to appear on defined field data types such as Text, Number, JSON, Boolean, File, Reference fields etc.
81+
82+
### Full Page Location
83+
84+
The Full Page location is a type of UI location that lets you view full page apps such as [Release Preview](https://www.contentstack.com/docs/developers/marketplace-apps/release-preview) within your stack.
85+
7686
## Using Contentstack styles
7787

7888
Install the Venus UI library package to style your app according to the Contentstack UI:
@@ -90,6 +100,12 @@ For more information on styling your application, refer to our [style guide](htt
90100
- [Marketplace Apps](https://www.contentstack.com/docs/developers/marketplace-apps/)
91101
- [Contentstack App Development](https://www.contentstack.com/docs/developers/developer-hub/)
92102

103+
## App SDK v2.0.0 Migration Guide
104+
105+
This guide provides instructions for migrating your application to App SDK version 2.0.0. It covers changes in metadata responses, field modifier and full page location updates, and the transition from the `_extension` property to `_uiLocation`. If you are upgrading your app to the latest version, make sure to follow these steps for a smooth transition.
106+
107+
[Read the Migration Guide](./docs/app-sdk-v2-migration.md)
108+
93109
## License
94110

95111
Licensed under [MIT](https://opensource.org/licenses/MIT).

SECURITY.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## Security
2+
3+
Contentstack takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations.
4+
5+
If you believe you have found a security vulnerability in any Contentstack-owned repository, please report it to us as described below.
6+
7+
## Reporting Security Issues
8+
9+
**Please do not report security vulnerabilities through public GitHub issues.**
10+
11+
Send email to [[email protected]](mailto:[email protected]).
12+
13+
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message.
14+
15+
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
16+
17+
- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
18+
- Full paths of source file(s) related to the manifestation of the issue
19+
- The location of the affected source code (tag/branch/commit or direct URL)
20+
- Any special configuration required to reproduce the issue
21+
- Step-by-step instructions to reproduce the issue
22+
- Proof-of-concept or exploit code (if possible)
23+
- Impact of the issue, including how an attacker might exploit the issue
24+
25+
This information will help us triage your report more quickly.
26+
27+
[https://www.contentstack.com/trust/](https://www.contentstack.com/trust/)

__test__/appConfig.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ describe("app config", () => {
1111
stack: {},
1212
};
1313
const appConfig: AppConfig = new AppConfig(
14-
mockData,
15-
mockConnection,
14+
mockData as any,
15+
mockConnection as any,
1616
mockEmitter,
1717
{ currentBranch: "master" }
1818
);
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
import EventEmitter from "wolfy87-eventemitter";
2+
3+
import AssetSidebarWidget from "../src/AssetSidebarWidget";
4+
import { IAssetSidebarInitData, LocationType } from "../src/types";
5+
import Asset from "../src/stack/api/asset";
6+
7+
jest.mock("post-robot", () => ({
8+
__esModule: true,
9+
default: {
10+
sendToParent: jest.fn(),
11+
},
12+
}));
13+
14+
jest.mock("../src/stack/api/asset");
15+
16+
describe("AssetSidebarWidget", () => {
17+
let assetSidebarWidget: AssetSidebarWidget;
18+
let mockInitData: IAssetSidebarInitData = {
19+
type: LocationType.ASSET_SIDEBAR_WIDGET,
20+
currentAsset: {} as any,
21+
config: {},
22+
app_id: "mock_app_uid",
23+
installation_uid: "mock_installation_uid",
24+
extension_uid: "mock_extension_uid",
25+
stack: {} as any,
26+
user: {} as any,
27+
currentBranch: "mock_branch",
28+
region: "region",
29+
};
30+
31+
let connection: { sendToParent: (...props: any[]) => any };
32+
let sendToParent;
33+
let emitter: EventEmitter;
34+
35+
beforeEach(function () {
36+
sendToParent = function () {
37+
return Promise.resolve({ data: {} });
38+
};
39+
40+
emitter = {
41+
on: (_event, cbf) => {
42+
setTimeout(() => {
43+
cbf({ state: "full_width" });
44+
}, 50);
45+
},
46+
} as EventEmitter;
47+
48+
jest.spyOn(emitter, "on");
49+
50+
connection = { sendToParent };
51+
jest.spyOn(connection, "sendToParent");
52+
assetSidebarWidget = new AssetSidebarWidget(
53+
mockInitData as IAssetSidebarInitData,
54+
connection as any,
55+
emitter
56+
);
57+
});
58+
59+
afterEach(() => {
60+
jest.resetAllMocks();
61+
});
62+
63+
it("should set instance properties in constructor", () => {
64+
expect(assetSidebarWidget.currentAsset).toBe(mockInitData.currentAsset);
65+
expect(assetSidebarWidget._emitter).toBe(emitter);
66+
expect(assetSidebarWidget._connection).toBe(connection);
67+
});
68+
69+
describe("getData", () => {
70+
it("should return the current asset", () => {
71+
const currentAsset = assetSidebarWidget.getData();
72+
expect(currentAsset).toBe(mockInitData.currentAsset);
73+
});
74+
});
75+
76+
describe("setData", () => {
77+
it("should set the current asset with the one provided", async () => {
78+
const asset = {};
79+
const result = await assetSidebarWidget.setData(asset);
80+
expect(connection.sendToParent).toHaveBeenCalledWith(
81+
"setData",
82+
asset
83+
);
84+
expect(result).toEqual(undefined);
85+
});
86+
});
87+
88+
describe("syncAsset", () => {
89+
it("should sync the upstream asset with the current", async () => {
90+
const result = await assetSidebarWidget.syncAsset();
91+
expect(connection.sendToParent).toHaveBeenCalledWith("syncAsset");
92+
expect(result).toEqual(undefined);
93+
});
94+
});
95+
96+
describe("updateWidth", () => {
97+
it("should throw an error if width is invalid", () => {
98+
const error = new Error("Width must be a number");
99+
expect(
100+
assetSidebarWidget.updateWidth("500" as any)
101+
).rejects.toThrowError(error);
102+
});
103+
104+
it("should update the width with the one provided", async () => {
105+
const mockWidth = 500;
106+
const result = await assetSidebarWidget.updateWidth(mockWidth);
107+
expect(connection.sendToParent).toHaveBeenCalledWith(
108+
"updateAssetSidebarWidth",
109+
mockWidth
110+
);
111+
expect(result).toEqual(undefined);
112+
});
113+
});
114+
115+
describe("replaceAsset", () => {
116+
it("should sync the upstream asset with the current", async () => {
117+
const mockHandleUpload = jest.fn();
118+
(Asset as jest.Mock).mockReturnValue({
119+
handleUpload: mockHandleUpload,
120+
});
121+
const file = {} as any;
122+
const result = await assetSidebarWidget.replaceAsset(file);
123+
expect(Asset).toHaveBeenLastCalledWith(emitter);
124+
expect(mockHandleUpload).toHaveBeenCalledWith([file], "replace");
125+
});
126+
});
127+
128+
describe("onSave", () => {
129+
it("should only accept functions as a callback", () => {
130+
const mockCallback: any = {};
131+
const error = new Error("Callback must be a function");
132+
expect(() => assetSidebarWidget.onSave(mockCallback)).toThrow(
133+
error
134+
);
135+
});
136+
137+
it("should setup a listener for assetSave event", () => {
138+
const mockCallback = jest.fn();
139+
const result = assetSidebarWidget.onSave(mockCallback);
140+
expect(emitter.on).toHaveBeenLastCalledWith(
141+
"assetSave",
142+
expect.any(Function)
143+
);
144+
expect(result).toBeUndefined();
145+
});
146+
147+
it("should invoke the callback provided ", () => {
148+
const mockCallback = jest.fn();
149+
jest.useFakeTimers();
150+
assetSidebarWidget.onSave(mockCallback);
151+
expect(emitter.on).toHaveBeenLastCalledWith(
152+
"assetSave",
153+
expect.any(Function)
154+
);
155+
jest.runAllTimers();
156+
expect(mockCallback).toHaveBeenCalled();
157+
});
158+
});
159+
160+
describe("onChange", () => {
161+
it("should only accept functions as a callback", () => {
162+
const mockCallback: any = {};
163+
const error = new Error("Callback must be a function");
164+
expect(() => assetSidebarWidget.onChange(mockCallback)).toThrow(
165+
error
166+
);
167+
});
168+
169+
it("should setup a listener for assetSave event", () => {
170+
const mockCallback = jest.fn();
171+
const result = assetSidebarWidget.onChange(mockCallback);
172+
expect(emitter.on).toHaveBeenLastCalledWith(
173+
"assetChange",
174+
expect.any(Function)
175+
);
176+
expect(result).toBeUndefined();
177+
});
178+
179+
it("should invoke the callback provided ", () => {
180+
const mockCallback = jest.fn();
181+
jest.useFakeTimers();
182+
assetSidebarWidget.onChange(mockCallback);
183+
expect(emitter.on).toHaveBeenLastCalledWith(
184+
"assetChange",
185+
expect.any(Function)
186+
);
187+
jest.runAllTimers();
188+
expect(mockCallback).toHaveBeenCalled();
189+
});
190+
});
191+
192+
describe("onPublish", () => {
193+
it("should only accept functions as a callback", () => {
194+
const mockCallback: any = {};
195+
const error = new Error("Callback must be a function");
196+
expect(() => assetSidebarWidget.onPublish(mockCallback)).toThrow(
197+
error
198+
);
199+
});
200+
201+
it("should setup a listener for assetSave event", () => {
202+
const mockCallback = jest.fn();
203+
const result = assetSidebarWidget.onPublish(mockCallback);
204+
expect(emitter.on).toHaveBeenLastCalledWith(
205+
"assetPublish",
206+
expect.any(Function)
207+
);
208+
expect(result).toBeUndefined();
209+
});
210+
211+
it("should invoke the callback provided ", () => {
212+
const mockCallback = jest.fn();
213+
jest.useFakeTimers();
214+
assetSidebarWidget.onPublish(mockCallback);
215+
expect(emitter.on).toHaveBeenLastCalledWith(
216+
"assetPublish",
217+
expect.any(Function)
218+
);
219+
jest.runAllTimers();
220+
expect(mockCallback).toHaveBeenCalled();
221+
});
222+
});
223+
224+
describe("onUnPublish", () => {
225+
it("should only accept functions as a callback", () => {
226+
const mockCallback: any = {};
227+
const error = new Error("Callback must be a function");
228+
expect(() => assetSidebarWidget.onUnPublish(mockCallback)).toThrow(
229+
error
230+
);
231+
});
232+
233+
it("should setup a listener for assetSave event", () => {
234+
const mockCallback = jest.fn();
235+
const result = assetSidebarWidget.onUnPublish(mockCallback);
236+
expect(emitter.on).toHaveBeenLastCalledWith(
237+
"assetUnPublish",
238+
expect.any(Function)
239+
);
240+
expect(result).toBeUndefined();
241+
});
242+
243+
it("should invoke the callback provided ", () => {
244+
const mockCallback = jest.fn();
245+
jest.useFakeTimers();
246+
assetSidebarWidget.onUnPublish(mockCallback);
247+
expect(emitter.on).toHaveBeenLastCalledWith(
248+
"assetUnPublish",
249+
expect.any(Function)
250+
);
251+
jest.runAllTimers();
252+
expect(mockCallback).toHaveBeenCalled();
253+
});
254+
});
255+
});

0 commit comments

Comments
 (0)