Skip to content

Commit 506d80b

Browse files
authored
Merge pull request #732 from gadget-inc/feature/shadcn-skeleton
Feature/shadcn skeleton
2 parents 309342b + ae8c51d commit 506d80b

29 files changed

+1326
-52
lines changed

packages/react/cypress/component/auto/AutoButton.cy.tsx

+20-13
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ describeForEachAutoAdapter("AutoButton", ({ name, adapter: { AutoButton }, wrapp
99
});
1010

1111
it("shows a button, runs a create action with no variables, and reports success", () => {
12-
cy.mountWithWrapper(<AutoButton action={api.widget.create} />, wrapper);
12+
cy.mountWithWrapper(<AutoButton id="auto" action={api.widget.create} />, wrapper);
1313
cy.contains("Create Widget");
1414

1515
cy.intercept("POST", `${api.connection.options.endpoint}?operation=createWidget`, {
@@ -23,7 +23,7 @@ describeForEachAutoAdapter("AutoButton", ({ name, adapter: { AutoButton }, wrapp
2323
},
2424
}).as("createWidget");
2525

26-
cy.get("button").click();
26+
cy.get("#auto").click();
2727

2828
cy.wait("@createWidget");
2929
cy.get("@createWidget").its("request.body.variables").should("deep.equal", { widget: {} });
@@ -32,7 +32,7 @@ describeForEachAutoAdapter("AutoButton", ({ name, adapter: { AutoButton }, wrapp
3232
});
3333

3434
it("shows a button, runs a create action with variables, and reports success", () => {
35-
cy.mountWithWrapper(<AutoButton action={api.widget.create} variables={{ widget: { name: "foobar" } }} />, wrapper);
35+
cy.mountWithWrapper(<AutoButton id="auto" action={api.widget.create} variables={{ widget: { name: "foobar" } }} />, wrapper);
3636
cy.contains("Create Widget");
3737

3838
cy.intercept("POST", `${api.connection.options.endpoint}?operation=createWidget`, {
@@ -46,7 +46,7 @@ describeForEachAutoAdapter("AutoButton", ({ name, adapter: { AutoButton }, wrapp
4646
},
4747
}).as("createWidget");
4848

49-
cy.get("button").click();
49+
cy.get("#auto").click();
5050

5151
cy.wait("@createWidget");
5252
cy.get("@createWidget")
@@ -59,22 +59,22 @@ describeForEachAutoAdapter("AutoButton", ({ name, adapter: { AutoButton }, wrapp
5959
});
6060

6161
it("shows a button, runs a create action with no variables, and reports an error if the network call fails", () => {
62-
cy.mountWithWrapper(<AutoButton action={api.widget.create} />, wrapper);
62+
cy.mountWithWrapper(<AutoButton id="auto" action={api.widget.create} />, wrapper);
6363
cy.contains("Create Widget");
6464

6565
cy.intercept("POST", `${api.connection.options.endpoint}?operation=createWidget`, {
6666
forceNetworkError: true,
6767
}).as("createWidget");
6868

69-
cy.get("button").click();
69+
cy.get("#auto").click();
7070

7171
cy.wait("@createWidget");
7272

7373
cy.contains("Create Widget encountered an error:");
7474
});
7575

7676
it("shows a button, runs an update action with variables, and reports success", () => {
77-
cy.mountWithWrapper(<AutoButton action={api.widget.update} variables={{ id: "123", widget: { name: "foobar" } }} />, wrapper);
77+
cy.mountWithWrapper(<AutoButton id="auto" action={api.widget.update} variables={{ id: "123", widget: { name: "foobar" } }} />, wrapper);
7878
cy.contains("Update Widget");
7979

8080
cy.intercept("POST", `${api.connection.options.endpoint}?operation=updateWidget`, {
@@ -88,7 +88,7 @@ describeForEachAutoAdapter("AutoButton", ({ name, adapter: { AutoButton }, wrapp
8888
},
8989
}).as("updateWidget");
9090

91-
cy.get("button").click();
91+
cy.get("#auto").click();
9292

9393
cy.wait("@updateWidget");
9494
cy.get("@updateWidget")
@@ -102,14 +102,20 @@ describeForEachAutoAdapter("AutoButton", ({ name, adapter: { AutoButton }, wrapp
102102
});
103103

104104
it("allows overriding the label", () => {
105-
cy.mountWithWrapper(<AutoButton action={api.widget.create}>Whizbang the flimflam</AutoButton>, wrapper);
105+
cy.mountWithWrapper(
106+
<AutoButton id="auto" action={api.widget.create}>
107+
Whizbang the flimflam
108+
</AutoButton>,
109+
wrapper
110+
);
106111
cy.contains("Whizbang the flimflam");
107112
});
108113

109114
it("allows overriding the onSuccess behaviour", () => {
110115
let onSuccessCalled = false;
111116
cy.mountWithWrapper(
112117
<AutoButton
118+
id="auto"
113119
action={api.widget.update}
114120
variables={{ id: "123", widget: { name: "foobar" } }}
115121
onSuccess={(result: any) => {
@@ -131,7 +137,7 @@ describeForEachAutoAdapter("AutoButton", ({ name, adapter: { AutoButton }, wrapp
131137
},
132138
}).as("updateWidget");
133139

134-
cy.get("button").click();
140+
cy.get("#auto").click();
135141

136142
cy.wait("@updateWidget").then(() => {
137143
expect(onSuccessCalled).to.be.true;
@@ -142,6 +148,7 @@ describeForEachAutoAdapter("AutoButton", ({ name, adapter: { AutoButton }, wrapp
142148
let onErrorCalled = false;
143149
cy.mountWithWrapper(
144150
<AutoButton
151+
id="auto"
145152
action={api.widget.update}
146153
variables={{ id: "123", widget: { name: "foobar" } }}
147154
onError={(error: Error) => {
@@ -157,15 +164,15 @@ describeForEachAutoAdapter("AutoButton", ({ name, adapter: { AutoButton }, wrapp
157164
forceNetworkError: true,
158165
}).as("updateWidget");
159166

160-
cy.get("button").click();
167+
cy.get("#auto").click();
161168

162169
cy.wait("@updateWidget").then(() => {
163170
expect(onErrorCalled).to.be.true;
164171
});
165172
});
166173

167174
it("shows a button, runs an global action with no variables, and reports success", () => {
168-
cy.mountWithWrapper(<AutoButton action={api.flipAll} />, wrapper);
175+
cy.mountWithWrapper(<AutoButton id="auto" action={api.flipAll} />, wrapper);
169176
cy.contains("Flip all");
170177

171178
cy.intercept("POST", `${api.connection.options.endpoint}?operation=flipAll`, {
@@ -176,7 +183,7 @@ describeForEachAutoAdapter("AutoButton", ({ name, adapter: { AutoButton }, wrapp
176183
},
177184
}).as("flipAll");
178185

179-
cy.get("button").click();
186+
cy.get("#auto").click();
180187

181188
cy.wait("@flipAll");
182189
cy.get("@flipAll").its("request.body.variables").should("deep.equal", {});

packages/react/cypress/component/auto/form/AutoFormDynamicFormInputChanges.cy.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ describeForEachAutoAdapter("AutoForm", ({ name, adapter: { AutoForm }, wrapper }
1212
<AutoForm action={api.widget.update} findBy={findBy} />
1313

1414
<div>
15-
<button id="setFindById1" onClick={() => setFindBy("1")} />
16-
<button id="setFindById2" onClick={() => setFindBy("2")} />
15+
<button style={{ width: 1, height: 1 }} id="setFindById1" onClick={() => setFindBy("1")} />
16+
<button style={{ width: 1, height: 1 }} id="setFindById2" onClick={() => setFindBy("2")} />
1717
</div>
1818
</>
1919
);

packages/react/cypress/component/auto/form/AutoFormInputLabels.cy.tsx

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
/* eslint-disable jest/valid-expect */
22
import React from "react";
3+
import { Input } from "../../../../spec/auto/shadcn-defaults/components/Input.js";
4+
import { Label } from "../../../../spec/auto/shadcn-defaults/components/Label.js";
35
import { PolarisAutoInput } from "../../../../src/auto/polaris/inputs/PolarisAutoInput.js";
6+
import { makeShadcnAutoInput } from "../../../../src/auto/shadcn/inputs/ShadcnAutoInput.js";
47
import { humanizeCamelCase } from "../../../../src/utils.js";
58
import { api } from "../../../support/api.js";
69
import { describeForEachAutoAdapter } from "../../../support/auto.js";
10+
import { SUITE_NAMES } from "../../../support/constants.js";
11+
12+
const ShadcnAutoInput = makeShadcnAutoInput({ Input, Label });
713

814
const AutoInput = (props: { suiteName: string; field: string; label?: string }) => {
9-
if (props.suiteName === "Polaris") {
15+
if (props.suiteName === SUITE_NAMES.POLARIS) {
1016
return <PolarisAutoInput {...props} />;
1117
}
1218

19+
if (props.suiteName === SUITE_NAMES.SHADCN) {
20+
return <ShadcnAutoInput {...props} />;
21+
}
22+
1323
throw new Error("Invalid suite name");
1424
};
1525

packages/react/cypress/support/auto.tsx

+26-2
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,23 @@ import "@shopify/polaris/build/esm/styles.css";
33
import translations from "@shopify/polaris/locales/en.json";
44
import type { ComponentType, ReactNode } from "react";
55
import React from "react";
6+
import { Toaster, elements } from "../../spec/auto/shadcn-defaults/index.js";
67
import type { AutoAdapter } from "../../src/auto/index.js";
78
import * as PolarisAdapter from "../../src/auto/polaris/index.js";
9+
import { makeAutocomponents } from "../../src/auto/shadcn/index.js";
810
import { FormProvider, useForm } from "../../src/useActionForm.js";
11+
import { SUITE_NAMES } from "./constants.js";
912

1013
interface AutoSuiteConfig {
1114
name: string;
1215
adapter: AutoAdapter;
1316
wrapper: ComponentType<{ children: ReactNode }>;
1417
}
1518

19+
const ONLY_RUN_SUITES = {
20+
[SUITE_NAMES.SHADCN]: ["AutoButton"],
21+
};
22+
1623
export const PolarisWrapper = ({ children }: { children: ReactNode }) => (
1724
<AppProvider i18n={translations}>
1825
<FormProvider {...useForm()}>
@@ -23,10 +30,27 @@ export const PolarisWrapper = ({ children }: { children: ReactNode }) => (
2330
</AppProvider>
2431
);
2532

26-
const suites: AutoSuiteConfig[] = [{ name: "Polaris", adapter: PolarisAdapter as any, wrapper: PolarisWrapper }];
33+
const ShadCNAdapter = makeAutocomponents(elements);
34+
35+
export const ShadcnWrapper = ({ children }: { children: ReactNode }) => (
36+
<>
37+
<FormProvider {...useForm()}>
38+
<Toaster />
39+
{children}
40+
</FormProvider>
41+
</>
42+
);
43+
44+
const suites: AutoSuiteConfig[] = [
45+
{ name: SUITE_NAMES.POLARIS, adapter: PolarisAdapter as any, wrapper: PolarisWrapper },
46+
{ name: SUITE_NAMES.SHADCN, adapter: ShadCNAdapter as any, wrapper: ShadcnWrapper },
47+
];
2748

2849
export const adapters = [PolarisAdapter];
2950
export const describeForEachAutoAdapter = (suiteName: string, suite: (config: AutoSuiteConfig) => void) => {
51+
const filteredSuites = suites.filter(
52+
(config) => config.name !== SUITE_NAMES.SHADCN || ONLY_RUN_SUITES[SUITE_NAMES.SHADCN].includes(suiteName)
53+
);
3054
// eslint-disable-next-line jest/valid-describe-callback, jest/valid-title
31-
describe.each(suites)((({ name }: { name: string }) => `${suiteName} - ${name}`) as any, suite);
55+
describe.each(filteredSuites)((({ name }: { name: string }) => `${suiteName} - ${name}`) as any, suite);
3256
};

packages/react/cypress/support/commands.ts

+17
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,20 @@ Cypress.Commands.add("mockUploadFile", (customToken) => {
9393
Cypress.Commands.add("getSubmitButton", () => {
9494
return cy.get("form [type=submit][aria-hidden!=true]");
9595
});
96+
97+
/**
98+
* Click on an element and type text into it
99+
* This command is used to simulate user interaction with the UI due to the fact that Cypress kept seeing some input elements as disabled
100+
* more on the issue here: https://github.com/cypress-io/cypress/issues/5830 and https://github.com/cypress-io/cypress/issues/5827
101+
*
102+
* @param selector - The selector of the element to click and type into
103+
* @param text - The text to type into the element
104+
* @param clear - Whether to clear the element before typing
105+
*/
106+
Cypress.Commands.add("clickAndType", { prevSubject: false }, (selector: string, text: string, clear = false) => {
107+
cy.get(selector).click();
108+
if (clear) {
109+
cy.get(selector).clear();
110+
}
111+
cy.get(selector).type(text);
112+
});

packages/react/cypress/support/component.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { ReactNode } from "react";
33
import React from "react";
44
import { Provider } from "../../src/index.js";
55
import "./commands.js";
6+
import "./cypress.css";
67

78
import { mount } from "cypress/react18";
89
import { api } from "./api.js";
@@ -57,7 +58,11 @@ before(() => {
5758
message.innerHTML = msg;
5859
document.body.appendChild(message);
5960
setTimeout(() => {
60-
document.body.removeChild(message);
61+
try {
62+
document.body.removeChild(message);
63+
} catch (e) {
64+
// don't worry if the element has already been removed or changed parents
65+
}
6166
}, 3000);
6267
},
6368
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export const SUITE_NAMES = {
2+
SHADCN: "Shadcn",
3+
POLARIS: "Polaris",
4+
} as const;
5+
6+
export const TEST_SUITES = {
7+
[SUITE_NAMES.SHADCN]: ["AutoButton"],
8+
} as const;

packages/react/cypress/support/cypress-commands.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ declare namespace Cypress {
2020
mockGetDirectUploadToken(api: any, customToken?: string): Chainable<void>;
2121
mockUploadFile: (customToken?: string) => Chainable<void>;
2222
getSubmitButton: () => Chainable<JQuery<HTMLElement>>;
23+
clickAndType: (selector: string, text: string, clear?: boolean) => Chainable<void>;
2324
}
2425
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;

packages/react/package.json

+19-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
"import": "./dist/esm/auto/polaris/index.js",
2222
"require": "./dist/cjs/auto/polaris/index.js",
2323
"default": "./dist/esm/auto/polaris/index.js"
24+
},
25+
"./auto/shadcn": {
26+
"import": "./dist/esm/auto/shadcn/index.js",
27+
"require": "./dist/cjs/auto/shadcn/index.js",
28+
"default": "./dist/esm/auto/shadcn/index.js"
2429
}
2530
},
2631
"source": "src/index.ts",
@@ -30,7 +35,7 @@
3035
"typecheck": "tsc --noEmit",
3136
"clean": "rimraf dist/ auto/",
3237
"build": "pnpm clean && pnpm setup-build && tsc -b tsconfig.cjs.json tsconfig.esm.json && pnpm copy",
33-
"setup-build": "mkdir -p dist/cjs dist/esm auto/polaris && echo '{\"type\": \"commonjs\"}' > dist/cjs/package.json && echo '{\"main\": \"../../dist/cjs/auto/polaris/index.js\"}' > auto/polaris/package.json && echo '{\"type\": \"module\"}' > dist/esm/package.json",
38+
"setup-build": "mkdir -p dist/cjs dist/esm auto/polaris auto/shadcn && echo '{\"type\": \"commonjs\"}' > dist/cjs/package.json && echo '{\"main\": \"../../dist/cjs/auto/polaris/index.js\"}' > auto/polaris/package.json && echo '{\"main\": \"../../dist/cjs/auto/shadcn/index.js\"}' > auto/shadcn/package.json && echo '{\"type\": \"module\"}' > dist/esm/package.json",
3439
"copy": "copyfiles -u 1 src/**/*.css dist/esm && copyfiles -u 1 src/**/*.css dist/cjs",
3540
"watch": "tsc -b tsconfig.esm.json --watch --preserveWatchOutput",
3641
"gql-gen": "graphql-codegen",
@@ -69,6 +74,8 @@
6974
"@pollyjs/adapter-xhr": "^6.0.6",
7075
"@pollyjs/core": "^6.0.6",
7176
"@pollyjs/persister-fs": "^6.0.6",
77+
"@radix-ui/react-toast": "^1.2.4",
78+
"@radix-ui/react-label": "^2.1.1",
7279
"@shopify/polaris": "^12.0.0",
7380
"@shopify/polaris-icons": "^8.1.0",
7481
"@storybook/addon-essentials": "^8.1.6",
@@ -91,23 +98,31 @@
9198
"@types/setup-polly-jest": "^0.5.5",
9299
"@types/tmp": "^0.2.6",
93100
"@urql/core": "^4.0.10",
101+
"autoprefixer": "^10.4.20",
102+
"class-variance-authority": "^0.7.1",
103+
"clsx": "^2.1.1",
94104
"conditional-type-checks": "^1.0.6",
95105
"copyfiles": "^2.4.1",
96106
"cypress": "^13.13.0",
97107
"cypress-each": "^1.13.3",
108+
"date-fns": "^2.30.0",
109+
"date-fns-tz": "^2.0.0",
98110
"execa": "^5.1.1",
99111
"graphql": "^16.8.1",
100112
"graphql-ws": "^5.13.1",
101113
"libnpmpack": "^7.0.4",
102114
"lodash-es": "^4.17.21",
115+
"lucide-react": "^0.471.0",
116+
"postcss": "^8.4.49",
103117
"react": "^18.2.0",
104118
"react-dom": "^18.2.0",
105119
"setup-polly-jest": "^0.11.0",
106120
"storybook": "^8.1.6",
121+
"tailwind-merge": "^2.6.0",
122+
"tailwindcss": "^3.4.17",
123+
"tailwindcss-animate": "^1.0.7",
107124
"tmp": "^0.2.3",
108-
"wonka": "^6.3.2",
109-
"date-fns": "^2.30.0",
110-
"date-fns-tz": "^2.0.0"
125+
"wonka": "^6.3.2"
111126
},
112127
"peerDependencies": {
113128
"@mdxeditor/editor": "^3.8.0",

packages/react/postcss.config.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default {
2+
plugins: {
3+
tailwindcss: {},
4+
autoprefixer: {},
5+
},
6+
};

0 commit comments

Comments
 (0)