create-nube-app 0.1.0 → 0.2.0
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/package.json +27 -33
- package/templates/minimal/.vscode/extensions.json +8 -0
- package/templates/minimal/.vscode/settings.json +15 -0
- package/templates/minimal/README.md +21 -7
- package/templates/minimal/biome.json +12 -0
- package/templates/minimal/package.json +22 -11
- package/templates/minimal/src/main.test.ts +42 -0
- package/templates/minimal/src/main.ts +1 -1
- package/templates/minimal/tsconfig.json +9 -9
- package/templates/minimal/tsup.config.js +10 -10
- package/templates/minimal/vitest.config.ts +5 -0
- package/templates/minimal-ui/.vscode/extensions.json +8 -0
- package/templates/minimal-ui/.vscode/settings.json +15 -0
- package/templates/minimal-ui/README.md +21 -7
- package/templates/minimal-ui/biome.json +12 -0
- package/templates/minimal-ui/package.json +24 -15
- package/templates/minimal-ui/src/components/MyCustomField.test.ts +51 -0
- package/templates/minimal-ui/src/components/MyCustomField.ts +18 -0
- package/templates/minimal-ui/src/main.test.ts +52 -0
- package/templates/minimal-ui/src/main.ts +8 -24
- package/templates/minimal-ui/vitest.config.ts +5 -0
- package/templates/minimal-ui-jsx/.vscode/extensions.json +8 -0
- package/templates/minimal-ui-jsx/.vscode/settings.json +15 -0
- package/templates/minimal-ui-jsx/README.md +21 -8
- package/templates/minimal-ui-jsx/biome.json +12 -0
- package/templates/minimal-ui-jsx/package.json +15 -4
- package/templates/minimal-ui-jsx/src/components/MyCustomField.test.tsx +61 -0
- package/templates/minimal-ui-jsx/src/components/MyCustomField.tsx +17 -0
- package/templates/minimal-ui-jsx/src/main.test.tsx +52 -0
- package/templates/minimal-ui-jsx/src/main.tsx +8 -24
- package/templates/minimal-ui-jsx/vitest.config.ts +5 -0
package/package.json
CHANGED
|
@@ -1,35 +1,29 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"./dist",
|
|
30
|
-
"README.md",
|
|
31
|
-
"CHANGELOG.md",
|
|
32
|
-
"LICENSE",
|
|
33
|
-
"./templates"
|
|
34
|
-
]
|
|
2
|
+
"name": "create-nube-app",
|
|
3
|
+
"description": "Create Nube App",
|
|
4
|
+
"author": "Tiendanube / Nuvemshop",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"version": "0.2.0",
|
|
7
|
+
"bin": {
|
|
8
|
+
"create-nube-app": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsup",
|
|
12
|
+
"check": "biome check src/*",
|
|
13
|
+
"check:fix": "biome check --write src/*"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@clack/prompts": "^0.10.0",
|
|
18
|
+
"fs-extra": "^11.1.1"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@biomejs/biome": "1.8.3",
|
|
22
|
+
"@types/fs-extra": "^11.0.4",
|
|
23
|
+
"@types/node": "^22.13.11",
|
|
24
|
+
"@types/prompts": "^2.4.9",
|
|
25
|
+
"tsup": "^8.4.0",
|
|
26
|
+
"typescript": "^5.6.2"
|
|
27
|
+
},
|
|
28
|
+
"files": ["./dist", "README.md", "CHANGELOG.md", "LICENSE", "./templates"]
|
|
35
29
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"editor.formatOnSave": true,
|
|
3
|
+
"[javascript]": {
|
|
4
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
5
|
+
},
|
|
6
|
+
"[javascriptreact]": {
|
|
7
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
8
|
+
},
|
|
9
|
+
"[typescript]": {
|
|
10
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
11
|
+
},
|
|
12
|
+
"[typescriptreact]": {
|
|
13
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
14
|
+
},
|
|
15
|
+
}
|
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Minimal App
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This is a minimalist project using TypeScript and Tiendanube tools.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Available Scripts
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- Then start the development changing the `src/main.ts` file.
|
|
9
|
-
- To compile the project use the `npm run build` command.
|
|
7
|
+
### Development
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
- `npm run dev` - Starts local development server
|
|
10
|
+
- `npm run build` - Builds the project using tsup
|
|
11
|
+
- `npm test` - Runs unit tests
|
|
12
|
+
- `npm run test:watch` - Runs tests in watch mode (automatically re-runs when changes are detected)
|
|
13
|
+
- `npm run test:coverage` - Runs tests and generates a coverage report
|
|
14
|
+
|
|
15
|
+
### Code Quality
|
|
16
|
+
|
|
17
|
+
- `npm run format` - Formats all project files using Biome
|
|
18
|
+
- `npm run lint` - Runs linting on all project files using Biome
|
|
19
|
+
|
|
20
|
+
## Technologies Used
|
|
21
|
+
|
|
22
|
+
- [TypeScript](https://www.typescriptlang.org/)
|
|
23
|
+
- [Vitest](https://vitest.dev/) for testing
|
|
24
|
+
- [Biome](https://biomejs.dev/) for formatting and linting
|
|
25
|
+
- [tsup](https://tsup.egoist.dev/) for building
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
"name": "template-minimal-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"dev": "concurrently -k -n BUILD,SERVE -c blue,green \"tsup --sourcemap inline --watch\" \"serve dist -l 8080 --cors\"",
|
|
6
|
+
"build": "tsup",
|
|
7
|
+
"test": "vitest run",
|
|
8
|
+
"test:watch": "vitest watch",
|
|
9
|
+
"test:coverage": "vitest run --coverage",
|
|
10
|
+
"format": "biome check --write ./src",
|
|
11
|
+
"lint": "biome check ./src"
|
|
12
|
+
},
|
|
13
|
+
"author": "Tiendanube / Nuvemshop",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@biomejs/biome": "^1.9.4",
|
|
16
|
+
"@tiendanube/nube-sdk-types": "0.4.0",
|
|
17
|
+
"@vitest/coverage-v8": "^3.0.9",
|
|
18
|
+
"concurrently": "^9.1.2",
|
|
19
|
+
"serve": "^14.2.4",
|
|
20
|
+
"tsup": "^8.3.0",
|
|
21
|
+
"typescript": "^5.6.2",
|
|
22
|
+
"vitest": "^3.0.9"
|
|
23
|
+
}
|
|
13
24
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { NubeSDK } from "@tiendanube/nube-sdk-types";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { App } from "./main";
|
|
4
|
+
|
|
5
|
+
describe("App", () => {
|
|
6
|
+
it("should register cart:update event handler", () => {
|
|
7
|
+
// Mock NubeSDK
|
|
8
|
+
const mockNube: Partial<NubeSDK> = {
|
|
9
|
+
on: vi.fn(),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Execute App function with mock
|
|
13
|
+
App(mockNube as NubeSDK);
|
|
14
|
+
|
|
15
|
+
// Verify if cart:update event was registered
|
|
16
|
+
expect(mockNube.on).toHaveBeenCalledWith(
|
|
17
|
+
"cart:update",
|
|
18
|
+
expect.any(Function),
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should log cart data when cart:update event is triggered", () => {
|
|
23
|
+
// Mock console.log
|
|
24
|
+
const consoleSpy = vi.spyOn(console, "log");
|
|
25
|
+
|
|
26
|
+
// Mock NubeSDK
|
|
27
|
+
const mockNube: Partial<NubeSDK> = {
|
|
28
|
+
on: vi.fn((event, handler) => {
|
|
29
|
+
if (event === "cart:update") {
|
|
30
|
+
// Simulate event being triggered
|
|
31
|
+
handler({ cart: { id: "123", total: 100 } });
|
|
32
|
+
}
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Execute App function with mock
|
|
37
|
+
App(mockNube as NubeSDK);
|
|
38
|
+
|
|
39
|
+
// Verify if console.log was called with cart data
|
|
40
|
+
expect(consoleSpy).toHaveBeenCalledWith({ id: "123", total: 100 });
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2016",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"esModuleInterop": true,
|
|
6
|
+
"forceConsistentCasingInFileNames": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { defineConfig } from "tsup";
|
|
2
2
|
|
|
3
3
|
export default defineConfig({
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
entry: ["./src/main.ts"],
|
|
5
|
+
clean: true,
|
|
6
|
+
format: ["esm"],
|
|
7
|
+
dts: false,
|
|
8
|
+
outDir: "./dist",
|
|
9
|
+
minify: true,
|
|
10
|
+
sourcemap: false,
|
|
11
|
+
outExtension: ({ options }) => ({
|
|
12
|
+
js: options.minify ? ".min.js" : ".js",
|
|
13
|
+
}),
|
|
14
14
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"editor.formatOnSave": true,
|
|
3
|
+
"[javascript]": {
|
|
4
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
5
|
+
},
|
|
6
|
+
"[javascriptreact]": {
|
|
7
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
8
|
+
},
|
|
9
|
+
"[typescript]": {
|
|
10
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
11
|
+
},
|
|
12
|
+
"[typescriptreact]": {
|
|
13
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
14
|
+
},
|
|
15
|
+
}
|
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Minimal App with UI
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This is a template project to develop custom apps for Tiendanube using declarative UI funcions
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Available Scripts
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- Then start the development changing the `src/main.ts` file.
|
|
9
|
-
- To compile the project use the `npm run build` command.
|
|
7
|
+
### Development
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
- `npm run dev` - Starts local development server
|
|
10
|
+
- `npm run build` - Builds the project using tsup
|
|
11
|
+
- `npm test` - Runs unit tests
|
|
12
|
+
- `npm run test:watch` - Runs tests in watch mode (automatically re-runs when changes are detected)
|
|
13
|
+
- `npm run test:coverage` - Runs tests and generates a coverage report
|
|
14
|
+
|
|
15
|
+
### Code Quality
|
|
16
|
+
|
|
17
|
+
- `npm run format` - Formats all project files using Biome
|
|
18
|
+
- `npm run lint` - Runs linting on all project files using Biome
|
|
19
|
+
|
|
20
|
+
## Technologies Used
|
|
21
|
+
|
|
22
|
+
- [TypeScript](https://www.typescriptlang.org/)
|
|
23
|
+
- [Vitest](https://vitest.dev/) for testing
|
|
24
|
+
- [Biome](https://biomejs.dev/) for formatting and linting
|
|
25
|
+
- [tsup](https://tsup.egoist.dev/) for building
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
2
|
+
"name": "template-minimal-ui-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "concurrently -k -n BUILD,SERVE -c blue,green \"tsup --sourcemap inline --watch\" \"serve dist -l 8080 --cors\"",
|
|
7
|
+
"build": "tsup",
|
|
8
|
+
"test": "vitest run",
|
|
9
|
+
"test:watch": "vitest watch",
|
|
10
|
+
"test:coverage": "vitest run --coverage",
|
|
11
|
+
"format": "biome check --write ./src",
|
|
12
|
+
"lint": "biome check ./src"
|
|
13
|
+
},
|
|
14
|
+
"author": "Tiendanube / Nuvemshop",
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@biomejs/biome": "^1.9.4",
|
|
17
|
+
"@tiendanube/nube-sdk-ui": "0.2.0",
|
|
18
|
+
"@tiendanube/nube-sdk-types": "0.4.0",
|
|
19
|
+
"@vitest/coverage-v8": "^3.0.9",
|
|
20
|
+
"concurrently": "^9.1.2",
|
|
21
|
+
"serve": "^14.2.4",
|
|
22
|
+
"tsup": "^8.3.0",
|
|
23
|
+
"typescript": "^5.6.2",
|
|
24
|
+
"vitest": "^3.0.9"
|
|
25
|
+
}
|
|
17
26
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
NubeComponent,
|
|
3
|
+
NubeComponentBox,
|
|
4
|
+
NubeComponentField,
|
|
5
|
+
NubeSDKState,
|
|
6
|
+
} from "@tiendanube/nube-sdk-types";
|
|
7
|
+
import { describe, expect, it, vi } from "vitest";
|
|
8
|
+
import { MyCustomField } from "./MyCustomField";
|
|
9
|
+
|
|
10
|
+
describe("MyCustomField", () => {
|
|
11
|
+
it("should render a Box with correct dimensions", () => {
|
|
12
|
+
const result = MyCustomField() as NubeComponentBox;
|
|
13
|
+
|
|
14
|
+
expect(result).toEqual({
|
|
15
|
+
type: "box",
|
|
16
|
+
width: 100,
|
|
17
|
+
height: 200,
|
|
18
|
+
children: expect.any(Array),
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should render a Field component with correct props", () => {
|
|
23
|
+
const result = MyCustomField() as NubeComponentBox;
|
|
24
|
+
const children = result.children as NubeComponent[];
|
|
25
|
+
const field = children[0] as NubeComponentField;
|
|
26
|
+
|
|
27
|
+
expect(field).toEqual({
|
|
28
|
+
type: "field",
|
|
29
|
+
id: "my-custom-field",
|
|
30
|
+
label: "Name",
|
|
31
|
+
name: "Name",
|
|
32
|
+
onChange: expect.any(Function),
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should log user input when field changes", () => {
|
|
37
|
+
const consoleSpy = vi.spyOn(console, "log");
|
|
38
|
+
const result = MyCustomField() as NubeComponentBox;
|
|
39
|
+
const children = result.children as NubeComponent[];
|
|
40
|
+
const field = children[0] as NubeComponentField;
|
|
41
|
+
|
|
42
|
+
// Simulate field change event
|
|
43
|
+
field.onChange?.({
|
|
44
|
+
type: "change",
|
|
45
|
+
state: {} as NubeSDKState,
|
|
46
|
+
value: "John Doe",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(consoleSpy).toHaveBeenCalledWith("User name: John Doe");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { box, field } from "@tiendanube/nube-sdk-ui";
|
|
2
|
+
|
|
3
|
+
export function MyCustomField() {
|
|
4
|
+
return box({
|
|
5
|
+
width: 100,
|
|
6
|
+
height: 200,
|
|
7
|
+
children: [
|
|
8
|
+
field({
|
|
9
|
+
id: "my-custom-field",
|
|
10
|
+
label: "Name",
|
|
11
|
+
name: "Name",
|
|
12
|
+
onChange: (e) => {
|
|
13
|
+
console.log(`User name: ${e.value}`);
|
|
14
|
+
},
|
|
15
|
+
}),
|
|
16
|
+
],
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { NubeSDK, NubeSDKState } from "@tiendanube/nube-sdk-types";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { App } from "./main";
|
|
4
|
+
|
|
5
|
+
// Mock do componente MyCustomField
|
|
6
|
+
vi.mock("./components/MyCustomField", () => ({
|
|
7
|
+
MyCustomField: vi.fn(() => ({
|
|
8
|
+
type: "box",
|
|
9
|
+
width: 100,
|
|
10
|
+
height: 100,
|
|
11
|
+
})),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe("App", () => {
|
|
15
|
+
it("should set MyCustomField component in after_line_items slot", () => {
|
|
16
|
+
// Mock NubeSDK
|
|
17
|
+
const mockNube: Partial<NubeSDK> = {
|
|
18
|
+
send: vi.fn(),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Execute App function with mock
|
|
22
|
+
App(mockNube as NubeSDK);
|
|
23
|
+
|
|
24
|
+
// Verify if send was called with correct parameters
|
|
25
|
+
expect(mockNube.send).toHaveBeenCalledWith(
|
|
26
|
+
"ui:slot:set",
|
|
27
|
+
expect.any(Function),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// Get the slot setter function
|
|
31
|
+
const slotSetter = (mockNube.send as ReturnType<typeof vi.fn>).mock
|
|
32
|
+
.calls[0][1] as (state: NubeSDKState) => {
|
|
33
|
+
ui: { slots: { after_line_items: unknown } };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Execute the slot setter function
|
|
37
|
+
const result = slotSetter({} as NubeSDKState);
|
|
38
|
+
|
|
39
|
+
// Verify if the slot was set with MyCustomField component
|
|
40
|
+
expect(result).toEqual({
|
|
41
|
+
ui: {
|
|
42
|
+
slots: {
|
|
43
|
+
after_line_items: expect.objectContaining({
|
|
44
|
+
type: "box",
|
|
45
|
+
width: 100,
|
|
46
|
+
height: 100,
|
|
47
|
+
}),
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -1,28 +1,12 @@
|
|
|
1
1
|
import type { NubeSDK } from "@tiendanube/nube-sdk-types";
|
|
2
|
-
import {
|
|
2
|
+
import { MyCustomField } from "./components/MyCustomField";
|
|
3
3
|
|
|
4
4
|
export function App(nube: NubeSDK) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
after_line_items:
|
|
13
|
-
box({
|
|
14
|
-
width: 100, height: 200, children: [
|
|
15
|
-
field({
|
|
16
|
-
id: "myField",
|
|
17
|
-
label: "Name",
|
|
18
|
-
name: "Name",
|
|
19
|
-
onChange: (e) => {
|
|
20
|
-
console.log("User name: " + e.value)
|
|
21
|
-
}
|
|
22
|
-
})
|
|
23
|
-
]
|
|
24
|
-
})
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}));
|
|
5
|
+
nube.send("ui:slot:set", () => ({
|
|
6
|
+
ui: {
|
|
7
|
+
slots: {
|
|
8
|
+
after_line_items: MyCustomField(),
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
28
12
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"editor.formatOnSave": true,
|
|
3
|
+
"[javascript]": {
|
|
4
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
5
|
+
},
|
|
6
|
+
"[javascriptreact]": {
|
|
7
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
8
|
+
},
|
|
9
|
+
"[typescript]": {
|
|
10
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
11
|
+
},
|
|
12
|
+
"[typescriptreact]": {
|
|
13
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
14
|
+
},
|
|
15
|
+
}
|
|
@@ -1,12 +1,25 @@
|
|
|
1
|
-
#
|
|
1
|
+
# JSX App
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This is a template project to develop custom apps for Tiendanube using JSX components
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Available Scripts
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- Then start the development changing the `src/main.tsx` file.
|
|
9
|
-
- To compile the project use the `npm run build` command.
|
|
10
|
-
- The contents of the `src/jsx` is required for JSX support, in the future we will provide a new `npm` package with the JSX support embedded.
|
|
7
|
+
### Development
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
- `npm run dev` - Starts local development server
|
|
10
|
+
- `npm run build` - Builds the project using tsup
|
|
11
|
+
- `npm test` - Runs unit tests
|
|
12
|
+
- `npm run test:watch` - Runs tests in watch mode (automatically re-runs when changes are detected)
|
|
13
|
+
- `npm run test:coverage` - Runs tests and generates a coverage report
|
|
14
|
+
|
|
15
|
+
### Code Quality
|
|
16
|
+
|
|
17
|
+
- `npm run format` - Formats all project files using Biome
|
|
18
|
+
- `npm run lint` - Runs linting on all project files using Biome
|
|
19
|
+
|
|
20
|
+
## Technologies Used
|
|
21
|
+
|
|
22
|
+
- [TypeScript](https://www.typescriptlang.org/)
|
|
23
|
+
- [Vitest](https://vitest.dev/) for testing
|
|
24
|
+
- [Biome](https://biomejs.dev/) for formatting and linting
|
|
25
|
+
- [tsup](https://tsup.egoist.dev/) for building
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "minimal-jsx-app",
|
|
3
|
-
"version": "1.0
|
|
2
|
+
"name": "template-minimal-jsx-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"
|
|
6
|
+
"dev": "concurrently -k -n BUILD,SERVE -c blue,green \"tsup --sourcemap inline --watch\" \"serve dist -l 8080 --cors\"",
|
|
7
|
+
"build": "tsup",
|
|
8
|
+
"test": "vitest run",
|
|
9
|
+
"test:watch": "vitest watch",
|
|
10
|
+
"test:coverage": "vitest run --coverage",
|
|
11
|
+
"format": "biome check --write ./src",
|
|
12
|
+
"lint": "biome check ./src"
|
|
7
13
|
},
|
|
8
14
|
"author": "Tiendanube / Nuvemshop",
|
|
9
15
|
"devDependencies": {
|
|
16
|
+
"@biomejs/biome": "^1.9.4",
|
|
10
17
|
"@tiendanube/nube-sdk-ui": "0.2.0",
|
|
11
18
|
"@tiendanube/nube-sdk-jsx": "0.2.1",
|
|
12
19
|
"@tiendanube/nube-sdk-types": "0.4.0",
|
|
20
|
+
"@vitest/coverage-v8": "^3.0.9",
|
|
21
|
+
"concurrently": "^9.1.2",
|
|
22
|
+
"serve": "^14.2.4",
|
|
13
23
|
"tsup": "^8.3.0",
|
|
14
|
-
"typescript": "^5.6.2"
|
|
24
|
+
"typescript": "^5.6.2",
|
|
25
|
+
"vitest": "^3.0.9"
|
|
15
26
|
}
|
|
16
27
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
NubeComponent,
|
|
3
|
+
NubeComponentBox,
|
|
4
|
+
NubeComponentField,
|
|
5
|
+
NubeSDKState,
|
|
6
|
+
} from "@tiendanube/nube-sdk-types";
|
|
7
|
+
import { describe, expect, it, vi } from "vitest";
|
|
8
|
+
import { MyCustomField } from "./MyCustomField";
|
|
9
|
+
|
|
10
|
+
describe("MyCustomField", () => {
|
|
11
|
+
it("should render a Box with correct dimensions", () => {
|
|
12
|
+
const result = MyCustomField() as NubeComponentBox;
|
|
13
|
+
|
|
14
|
+
expect(result).toEqual({
|
|
15
|
+
type: "box",
|
|
16
|
+
width: 100,
|
|
17
|
+
height: 200,
|
|
18
|
+
children: expect.any(Array),
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should render a Text component with correct content", () => {
|
|
23
|
+
const result = MyCustomField() as NubeComponentBox;
|
|
24
|
+
const children = result.children as NubeComponent[];
|
|
25
|
+
|
|
26
|
+
expect(children[0]).toEqual({
|
|
27
|
+
type: "txt",
|
|
28
|
+
children: "Hello!!",
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should render a Field component with correct props", () => {
|
|
33
|
+
const result = MyCustomField() as NubeComponentBox;
|
|
34
|
+
const children = result.children as NubeComponent[];
|
|
35
|
+
const field = children[1] as NubeComponentField;
|
|
36
|
+
|
|
37
|
+
expect(field).toEqual({
|
|
38
|
+
type: "field",
|
|
39
|
+
id: "my-custom-field",
|
|
40
|
+
label: "Name",
|
|
41
|
+
name: "Name",
|
|
42
|
+
onChange: expect.any(Function),
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should log user input when field changes", () => {
|
|
47
|
+
const consoleSpy = vi.spyOn(console, "log");
|
|
48
|
+
const result = MyCustomField() as NubeComponentBox;
|
|
49
|
+
const children = result.children as NubeComponent[];
|
|
50
|
+
const field = children[1] as NubeComponentField;
|
|
51
|
+
|
|
52
|
+
// Simulate field change event
|
|
53
|
+
field.onChange?.({
|
|
54
|
+
type: "change",
|
|
55
|
+
state: {} as NubeSDKState,
|
|
56
|
+
value: "John Doe",
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(consoleSpy).toHaveBeenCalledWith("User name: John Doe");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Box, Field, Txt } from "@tiendanube/nube-sdk-jsx";
|
|
2
|
+
|
|
3
|
+
export function MyCustomField() {
|
|
4
|
+
return (
|
|
5
|
+
<Box width={100} height={200}>
|
|
6
|
+
<Txt>Hello!!</Txt>
|
|
7
|
+
<Field
|
|
8
|
+
id="my-custom-field"
|
|
9
|
+
label="Name"
|
|
10
|
+
name="Name"
|
|
11
|
+
onChange={(e) => {
|
|
12
|
+
console.log(`User name: ${e.value}`);
|
|
13
|
+
}}
|
|
14
|
+
/>
|
|
15
|
+
</Box>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { NubeSDK, NubeSDKState } from "@tiendanube/nube-sdk-types";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { App } from "./main";
|
|
4
|
+
|
|
5
|
+
// Mock do componente MyCustomField
|
|
6
|
+
vi.mock("./components/MyCustomField", () => ({
|
|
7
|
+
MyCustomField: vi.fn(() => ({
|
|
8
|
+
type: "box",
|
|
9
|
+
width: 100,
|
|
10
|
+
height: 100,
|
|
11
|
+
})),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe("App", () => {
|
|
15
|
+
it("should set MyCustomField component in after_line_items slot", () => {
|
|
16
|
+
// Mock NubeSDK
|
|
17
|
+
const mockNube: Partial<NubeSDK> = {
|
|
18
|
+
send: vi.fn(),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Execute App function with mock
|
|
22
|
+
App(mockNube as NubeSDK);
|
|
23
|
+
|
|
24
|
+
// Verify if send was called with correct parameters
|
|
25
|
+
expect(mockNube.send).toHaveBeenCalledWith(
|
|
26
|
+
"ui:slot:set",
|
|
27
|
+
expect.any(Function),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// Get the slot setter function
|
|
31
|
+
const slotSetter = (mockNube.send as ReturnType<typeof vi.fn>).mock
|
|
32
|
+
.calls[0][1] as (state: NubeSDKState) => {
|
|
33
|
+
ui: { slots: { after_line_items: unknown } };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Execute the slot setter function
|
|
37
|
+
const result = slotSetter({} as NubeSDKState);
|
|
38
|
+
|
|
39
|
+
// Verify if the slot was set with MyCustomField component
|
|
40
|
+
expect(result).toEqual({
|
|
41
|
+
ui: {
|
|
42
|
+
slots: {
|
|
43
|
+
after_line_items: expect.objectContaining({
|
|
44
|
+
type: "box",
|
|
45
|
+
width: 100,
|
|
46
|
+
height: 100,
|
|
47
|
+
}),
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -1,28 +1,12 @@
|
|
|
1
1
|
import type { NubeSDK } from "@tiendanube/nube-sdk-types";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
function MyComponent() {
|
|
5
|
-
return (
|
|
6
|
-
<Box width={100} height={200}>
|
|
7
|
-
<Txt>Hello!!</Txt>
|
|
8
|
-
<Field
|
|
9
|
-
id="myField"
|
|
10
|
-
label="Name"
|
|
11
|
-
name="Name"
|
|
12
|
-
onChange={(e) => {
|
|
13
|
-
console.log(`User name: ${e.value}`);
|
|
14
|
-
}}
|
|
15
|
-
/>
|
|
16
|
-
</Box>
|
|
17
|
-
);
|
|
18
|
-
}
|
|
2
|
+
import { MyCustomField } from "./components/MyCustomField";
|
|
19
3
|
|
|
20
4
|
export function App(nube: NubeSDK) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
5
|
+
nube.send("ui:slot:set", () => ({
|
|
6
|
+
ui: {
|
|
7
|
+
slots: {
|
|
8
|
+
after_line_items: <MyCustomField />,
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
28
12
|
}
|