create-oc 0.0.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.
Files changed (49) hide show
  1. package/index.js +63 -0
  2. package/package.json +16 -0
  3. package/templates/elm/elm.json +26 -0
  4. package/templates/elm/package.json +31 -0
  5. package/templates/elm/src/Main.elm +180 -0
  6. package/templates/elm/src/server.ts +22 -0
  7. package/templates/elm/tsconfig.json +18 -0
  8. package/templates/es6/css/styles.css +3 -0
  9. package/templates/es6/img/logo.png +0 -0
  10. package/templates/es6/oc-app.d.ts +1 -0
  11. package/templates/es6/package.json +31 -0
  12. package/templates/es6/server.ts +11 -0
  13. package/templates/es6/tsconfig.json +17 -0
  14. package/templates/es6/view.ts +10 -0
  15. package/templates/preact/.eslintrc.json +3 -0
  16. package/templates/preact/package.json +42 -0
  17. package/templates/preact/public/logo.png +0 -0
  18. package/templates/preact/src/App.test.tsx +28 -0
  19. package/templates/preact/src/App.tsx +52 -0
  20. package/templates/preact/src/oc-app.d.ts +1 -0
  21. package/templates/preact/src/server.ts +35 -0
  22. package/templates/preact/src/setupTests.js +1 -0
  23. package/templates/preact/src/styles.css +30 -0
  24. package/templates/preact/src/types.ts +15 -0
  25. package/templates/preact/tsconfig.json +27 -0
  26. package/templates/preact/vite.config.ts +11 -0
  27. package/templates/react/.eslintrc.json +3 -0
  28. package/templates/react/package.json +44 -0
  29. package/templates/react/public/logo.png +0 -0
  30. package/templates/react/src/App.test.tsx +39 -0
  31. package/templates/react/src/App.tsx +52 -0
  32. package/templates/react/src/oc-app.d.ts +1 -0
  33. package/templates/react/src/server.ts +37 -0
  34. package/templates/react/src/setupTests.js +1 -0
  35. package/templates/react/src/styles.css +30 -0
  36. package/templates/react/src/types.ts +15 -0
  37. package/templates/react/tsconfig.json +26 -0
  38. package/templates/react/vite.config.ts +11 -0
  39. package/templates/solid/package.json +42 -0
  40. package/templates/solid/public/logo.png +0 -0
  41. package/templates/solid/src/App.test.tsx +37 -0
  42. package/templates/solid/src/App.tsx +50 -0
  43. package/templates/solid/src/oc-app.d.ts +1 -0
  44. package/templates/solid/src/server.ts +36 -0
  45. package/templates/solid/src/setupTests.js +1 -0
  46. package/templates/solid/src/styles.css +30 -0
  47. package/templates/solid/src/types.ts +15 -0
  48. package/templates/solid/tsconfig.json +27 -0
  49. package/templates/solid/vite.config.ts +20 -0
package/index.js ADDED
@@ -0,0 +1,63 @@
1
+ import prompts from 'prompts';
2
+ import fs from 'fs';
3
+
4
+ const { componentName, template } = await prompts([
5
+ {
6
+ type: 'text',
7
+ name: 'componentName',
8
+ message: 'Component name',
9
+ initial: 'oc-component',
10
+ },
11
+ {
12
+ type: 'select',
13
+ name: 'template',
14
+ message: 'Select a template',
15
+ choices: [
16
+ { title: 'Vanilla', value: 'es6' },
17
+ { title: 'React', value: 'react' },
18
+ { title: 'Solid', value: 'solid' },
19
+ { title: 'Preact', value: 'preact' },
20
+ { title: 'Elm', value: 'elm' },
21
+ ],
22
+ },
23
+ ]);
24
+
25
+ try {
26
+ fs.mkdirSync(`./${componentName}`);
27
+ } catch (err) {
28
+ if (err.code === 'EEXIST') {
29
+ console.log('Folder already exists');
30
+ process.exit(1);
31
+ }
32
+ throw err;
33
+ }
34
+
35
+ console.log();
36
+ console.log('Creating the template');
37
+ fs.cpSync(`./templates/${template}`, `./${componentName}`, {
38
+ recursive: true,
39
+ });
40
+ fs.writeFileSync(
41
+ `./${componentName}/.gitignore`,
42
+ ['node_modules', '_package'].join('\n'),
43
+ 'utf-8'
44
+ );
45
+ replaceJson(`./${componentName}/package.json`, (pkg) => ({
46
+ ...pkg,
47
+ scripts: {
48
+ ...(pkg.scripts || {}),
49
+ start: `oc dev .. --components ${componentName}`,
50
+ },
51
+ }));
52
+
53
+ console.log('Finished. To start your oc for the first time:');
54
+ console.log();
55
+ console.log(` cd ${componentName}`);
56
+ console.log(' npm install');
57
+ console.log(' npm start');
58
+
59
+ function replaceJson(file, transformer) {
60
+ const json = JSON.parse(fs.readFileSync(file, 'utf-8'));
61
+ const transformed = transformer(json);
62
+ fs.writeFileSync(file, JSON.stringify(transformed, null, 2), 'utf-8');
63
+ }
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "create-oc",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "description": "",
6
+ "main": "index.js",
7
+ "scripts": {
8
+ "test": "echo \"Error: no test specified\" && exit 1"
9
+ },
10
+ "keywords": [],
11
+ "author": "",
12
+ "license": "ISC",
13
+ "dependencies": {
14
+ "prompts": "^2.4.2"
15
+ }
16
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "type": "application",
3
+ "source-directories": [
4
+ "src"
5
+ ],
6
+ "elm-version": "0.19.1",
7
+ "dependencies": {
8
+ "direct": {
9
+ "elm/browser": "1.0.2",
10
+ "elm/core": "1.0.5",
11
+ "elm/html": "1.0.0",
12
+ "elm/json": "1.1.3",
13
+ "rtfeldman/elm-css": "16.1.1"
14
+ },
15
+ "indirect": {
16
+ "elm/time": "1.0.0",
17
+ "elm/url": "1.0.0",
18
+ "elm/virtual-dom": "1.0.2",
19
+ "rtfeldman/elm-hex": "1.0.0"
20
+ }
21
+ },
22
+ "test-dependencies": {
23
+ "direct": {},
24
+ "indirect": {}
25
+ }
26
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "base-component-elm",
3
+ "description": "",
4
+ "version": "1.0.0",
5
+ "scripts": {},
6
+ "oc": {
7
+ "files": {
8
+ "data": "src/server.ts",
9
+ "template": {
10
+ "src": "src/Main.elm",
11
+ "type": "oc-template-elm"
12
+ }
13
+ },
14
+ "parameters": {
15
+ "userId": {
16
+ "default": 1,
17
+ "description": "The user id from the user database",
18
+ "example": 1,
19
+ "mandatory": true,
20
+ "type": "number"
21
+ }
22
+ }
23
+ },
24
+ "dependencies": {},
25
+ "devDependencies": {
26
+ "oc-server": "*",
27
+ "oc-template-elm-compiler": "*",
28
+ "elm": "*",
29
+ "typescript": "*"
30
+ }
31
+ }
@@ -0,0 +1,180 @@
1
+ port module Main exposing (..)
2
+
3
+ import Browser
4
+ import Css exposing (..)
5
+ import Html.Styled exposing (..)
6
+ import Html.Styled.Attributes exposing (..)
7
+ import Html.Styled.Events exposing (onClick)
8
+ import Json.Decode as D
9
+ import Json.Encode as E
10
+
11
+
12
+
13
+ -- MAIN
14
+
15
+
16
+ main : Program E.Value Model Msg
17
+ main =
18
+ Browser.element
19
+ { init = init
20
+ , view = view >> toUnstyled
21
+ , update = update
22
+ , subscriptions = subscriptions
23
+ }
24
+
25
+
26
+
27
+ -- MODEL
28
+
29
+
30
+ type alias Model =
31
+ { userId : Int, name : String, email : String, age : Maybe Int }
32
+
33
+
34
+ type alias DataRequest =
35
+ { userId : Int }
36
+
37
+
38
+ encode : DataRequest -> E.Value
39
+ encode data =
40
+ E.object
41
+ [ ( "userId", E.int data.userId )
42
+ , ( "moreData", E.bool True )
43
+ ]
44
+
45
+
46
+ init : E.Value -> ( Model, Cmd Msg )
47
+ init flags =
48
+ ( case D.decodeValue decoder flags of
49
+ Ok model ->
50
+ model
51
+
52
+ Err _ ->
53
+ { name = "", email = "", userId = 0, age = Nothing }
54
+ , Cmd.none
55
+ )
56
+
57
+
58
+ isJust : Maybe a -> Bool
59
+ isJust data =
60
+ case data of
61
+ Nothing ->
62
+ False
63
+
64
+ _ ->
65
+ True
66
+
67
+
68
+
69
+ -- UPDATE
70
+
71
+
72
+ type Msg
73
+ = GetData
74
+ | Recv E.Value
75
+
76
+
77
+ update : Msg -> Model -> ( Model, Cmd Msg )
78
+ update msg model =
79
+ case msg of
80
+ GetData ->
81
+ ( model, DataRequest model.userId |> encode |> getData )
82
+
83
+ Recv data ->
84
+ ( case D.decodeValue decoder data of
85
+ Ok newModel ->
86
+ newModel
87
+
88
+ Err _ ->
89
+ { name = "", email = "", userId = 0, age = Nothing }
90
+ , Cmd.none
91
+ )
92
+
93
+
94
+
95
+ -- VIEW
96
+
97
+
98
+ inputStyle : Style
99
+ inputStyle =
100
+ Css.batch
101
+ [ border3 (px 5) solid (rgb 255 240 255)
102
+ , boxShadow5 inset zero zero (px 8) (rgba 0 0 0 0.1)
103
+ , padding (px 15)
104
+ , backgroundColor (rgba 255 255 255 0.5)
105
+ , margin (px 10)
106
+ ]
107
+
108
+
109
+ htmlIf : Html msg -> Bool -> Html msg
110
+ htmlIf el cond =
111
+ if cond then
112
+ el
113
+
114
+ else
115
+ text ""
116
+
117
+
118
+ view : Model -> Html Msg
119
+ view model =
120
+ div
121
+ [ css
122
+ [ padding (px 20)
123
+ ]
124
+ ]
125
+ [ input
126
+ [ css [ inputStyle ]
127
+ , type_ "text"
128
+ , placeholder "Name"
129
+ , value model.name
130
+ ]
131
+ []
132
+ , input
133
+ [ css [ inputStyle ]
134
+ , type_ "text"
135
+ , placeholder "Email"
136
+ , value model.email
137
+ ]
138
+ []
139
+ , case model.age of
140
+ Nothing ->
141
+ text ""
142
+
143
+ Just age ->
144
+ input
145
+ [ css [ inputStyle ]
146
+ , type_ "number"
147
+ , placeholder "Age"
148
+ , String.fromInt age |> value
149
+ ]
150
+ []
151
+ , button [ onClick GetData ] [ text "Get more data" ]
152
+ ]
153
+
154
+
155
+
156
+ -- SUBSCRIPTIONS
157
+
158
+
159
+ subscriptions : Model -> Sub Msg
160
+ subscriptions _ =
161
+ dataReceiver Recv
162
+
163
+
164
+ decoder : D.Decoder Model
165
+ decoder =
166
+ D.map4 Model
167
+ (D.field "userId" D.int)
168
+ (D.field "name" D.string)
169
+ (D.field "email" D.string)
170
+ (D.maybe (D.field "age" D.int))
171
+
172
+
173
+
174
+ -- PORTS
175
+
176
+
177
+ port getData : E.Value -> Cmd msg
178
+
179
+
180
+ port dataReceiver : (E.Value -> msg) -> Sub msg
@@ -0,0 +1,22 @@
1
+ import { Server } from 'oc-server';
2
+
3
+ const database = [
4
+ { name: 'John Doe', email: 'john@doe.com', age: 32 },
5
+ { name: 'Jane Doe', email: 'jane@doe.com', age: 31 }
6
+ ];
7
+
8
+ async function getUser(userId: number) {
9
+ return database[userId];
10
+ }
11
+
12
+ export const server = new Server(async (params: { userId: number; moreData?: boolean }) => {
13
+ const { moreData, userId } = params;
14
+ const { email, name, age } = await getUser(userId);
15
+
16
+ return {
17
+ userId,
18
+ name,
19
+ email,
20
+ age: moreData && age
21
+ };
22
+ });
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "esModuleInterop": true,
8
+ "allowSyntheticDefaultImports": true,
9
+ "strict": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "module": "esnext",
12
+ "moduleResolution": "node",
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true
15
+ },
16
+ "include": ["src"],
17
+ "exclude": ["src/_package"]
18
+ }
@@ -0,0 +1,3 @@
1
+ .awesome {
2
+ background: palevioletred;
3
+ }
Binary file
@@ -0,0 +1 @@
1
+ /// <reference types="oc-vite/client" />
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "base-component-es6",
3
+ "description": "",
4
+ "version": "1.0.0",
5
+ "oc": {
6
+ "files": {
7
+ "data": "server.js",
8
+ "static": [
9
+ "img",
10
+ "css"
11
+ ],
12
+ "template": {
13
+ "src": "view.js",
14
+ "type": "oc-template-es6"
15
+ }
16
+ },
17
+ "parameters": {
18
+ "name": {
19
+ "default": "Jane Doe",
20
+ "description": "Your name",
21
+ "example": "Jane Doe",
22
+ "mandatory": false,
23
+ "type": "string"
24
+ }
25
+ }
26
+ },
27
+ "devDependencies": {
28
+ "oc-server": "*",
29
+ "oc-template-es6-compiler": "*"
30
+ }
31
+ }
@@ -0,0 +1,11 @@
1
+ import { Server } from "oc-server";
2
+
3
+ export const server = new Server((params: { name: string }, context) => {
4
+ const { name } = params;
5
+ const { staticPath } = context;
6
+
7
+ return {
8
+ name,
9
+ staticPath,
10
+ };
11
+ });
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "strict": true,
6
+ "allowJs": true,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": true,
9
+ "allowSyntheticDefaultImports": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "noFallthroughCasesInSwitch": true,
12
+ "module": "esnext",
13
+ "moduleResolution": "node",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true
16
+ }
17
+ }
@@ -0,0 +1,10 @@
1
+ import logo from "./img/logo.png";
2
+ import styles from "./css/styles.css";
3
+
4
+ export default (props: { name: string }) => {
5
+ console.log("PR::", props);
6
+ return `<div class=${styles.awesome}>
7
+ Hello ${props.name}!
8
+ <img src="${logo}" />
9
+ </div>`;
10
+ };
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": ["react-app"]
3
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "base-component-react",
3
+ "description": "",
4
+ "version": "1.0.0",
5
+ "scripts": {
6
+ "lint": "eslint --max-warnings 0 --ext .js,.jsx,.ts,.tsx src",
7
+ "build": "tsc --noEmit && oc package .",
8
+ "test": "vitest"
9
+ },
10
+ "oc": {
11
+ "files": {
12
+ "data": "src/server.ts",
13
+ "template": {
14
+ "src": "src/App.tsx",
15
+ "type": "oc-template-preact"
16
+ },
17
+ "static": [
18
+ "public"
19
+ ]
20
+ },
21
+ "parameters": {
22
+ "userId": {
23
+ "default": 1,
24
+ "description": "The user id from the user database",
25
+ "example": 1,
26
+ "mandatory": true,
27
+ "type": "number"
28
+ }
29
+ }
30
+ },
31
+ "dependencies": {},
32
+ "devDependencies": {
33
+ "@testing-library/react": "^14.0.0",
34
+ "@testing-library/user-event": "^14.4.3",
35
+ "jsdom": "^21.1.1",
36
+ "oc-server": "*",
37
+ "oc-template-preact-compiler": "0.2.0",
38
+ "preact": "^10.13.2",
39
+ "typescript": "5.0.2",
40
+ "vitest": "^0.29.7"
41
+ }
42
+ }
Binary file
@@ -0,0 +1,28 @@
1
+ import { describe, it, afterEach, beforeEach, expect, vi } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { DataProvider } from 'oc-template-preact-compiler/dist/utils/useData';
4
+
5
+ import App from './App';
6
+
7
+ const getData = vi.fn();
8
+
9
+ describe('App - Page', () => {
10
+ beforeEach(() => {
11
+ window.oc = { events: { on: vi.fn(), fire: vi.fn() } } as any;
12
+ });
13
+ afterEach(() => {
14
+ vi.restoreAllMocks();
15
+ });
16
+
17
+ it('Gets more data when clicking the button', () => {
18
+ getData.mockImplementationOnce(() => Promise.resolve({}));
19
+ render(
20
+ <DataProvider value={{ firstName: 'firstName', lastName: 'lastName', userId: 0, getData }}>
21
+ <App firstName="firstName" lastName="lastName" userId={0} />
22
+ </DataProvider>
23
+ );
24
+
25
+ const extraInfoButton = screen.getByText(/Get extra information/i);
26
+ expect(extraInfoButton).toBeTruthy();
27
+ });
28
+ });
@@ -0,0 +1,52 @@
1
+ import { useState } from 'preact/hooks';
2
+ import { useData } from 'oc-template-preact-compiler/dist/utils/useData';
3
+ import { serverClient } from 'oc-server';
4
+ import styles from './styles.css';
5
+ import type { AdditionalData, ClientProps } from './types';
6
+
7
+ interface AppProps extends ClientProps {
8
+ getMoreData?: boolean;
9
+ }
10
+
11
+ const App = () => {
12
+ const { firstName, lastName, userId, getData, getSetting } = useData<AppProps>();
13
+ const staticPath = getSetting('staticPath');
14
+ const [additionalData, setAdditionalData] = useState<AdditionalData | null>(null);
15
+ const [error, setError] = useState('');
16
+
17
+ const fetchMoreData = async () => {
18
+ setError('');
19
+ try {
20
+ const data = await serverClient.getMoreData({ userId });
21
+ setAdditionalData(data);
22
+ } catch (err) {
23
+ setError(String(err));
24
+ }
25
+ };
26
+
27
+ if (error) {
28
+ return <div>Something wrong happened!</div>;
29
+ }
30
+
31
+ return (
32
+ <div className={styles.container}>
33
+ <img width="50" height="50" src={`${staticPath}/public/logo.png`} alt="Logo" />
34
+ <h1 style={{ margin: '0 0 20px 0' }}>
35
+ Hello, <span style={{ textDecoration: 'underline' }}>{firstName}</span> {lastName}
36
+ </h1>
37
+ {additionalData && (
38
+ <div className={styles.info}>
39
+ <div className={styles.block}>Age: {additionalData.age}</div>
40
+ <div className={styles.block}>
41
+ Hobbies: {additionalData.hobbies.map((x) => x.toLowerCase()).join(', ')}
42
+ </div>
43
+ </div>
44
+ )}
45
+ <button className={styles.button} onClick={fetchMoreData}>
46
+ Get extra information
47
+ </button>
48
+ </div>
49
+ );
50
+ };
51
+
52
+ export default App;
@@ -0,0 +1 @@
1
+ /// <reference types="oc-vite/client" />
@@ -0,0 +1,35 @@
1
+ import { Server } from 'oc-server';
2
+ import { AdditionalData, ClientProps, OcParameters } from './types';
3
+
4
+ const database = [
5
+ { name: 'John Doe', age: 34, hobbies: ['Swimming', 'Basketball'] },
6
+ { name: 'Jane Doe', age: 35, hobbies: ['Running', 'Rugby'] }
7
+ ];
8
+
9
+ async function getUser(userId: number) {
10
+ return database[userId];
11
+ }
12
+
13
+ export const server = new Server(async (params: OcParameters): Promise<ClientProps> => {
14
+ const user = await getUser(params.userId);
15
+ const [firstName, lastName] = user.name.split(/\s+/);
16
+
17
+ return {
18
+ userId: params.userId,
19
+ firstName,
20
+ lastName
21
+ };
22
+ }).action('getMoreData', async (params: OcParameters): Promise<AdditionalData> => {
23
+ const user = await getUser(params.userId);
24
+
25
+ return {
26
+ age: user.age,
27
+ hobbies: user.hobbies
28
+ };
29
+ });
30
+
31
+ declare module 'oc-server' {
32
+ interface Register {
33
+ server: typeof server;
34
+ }
35
+ }
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1,30 @@
1
+ .container {
2
+ background-color: #3b246c;
3
+ color: #fff;
4
+ font-family: sans-serif;
5
+ padding: 40px;
6
+ }
7
+
8
+ .button {
9
+ background-color: #a97613;
10
+ border: none;
11
+ padding: 15px 32px;
12
+ text-align: center;
13
+ font-size: 16px;
14
+ text-decoration: none;
15
+ display: inline-block;
16
+ color: inherit;
17
+ cursor: pointer;
18
+ }
19
+
20
+ .info {
21
+ margin-bottom: 20px;
22
+ }
23
+
24
+ .block {
25
+ margin: 6px 0;
26
+ }
27
+
28
+ .button:hover {
29
+ background-color: #c79535;
30
+ }
@@ -0,0 +1,15 @@
1
+ export interface OcParameters {
2
+ userId: number;
3
+ getMoreData?: boolean;
4
+ }
5
+
6
+ export interface AdditionalData {
7
+ hobbies: string[];
8
+ age: number;
9
+ }
10
+
11
+ export interface ClientProps extends Partial<AdditionalData> {
12
+ userId: number;
13
+ firstName: string;
14
+ lastName: string;
15
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ // These are suggested values and will be set when not present in the
4
+ // tsconfig.json
5
+ // 'parsedValue' matches the output value from ts.parseJsonConfigFileContent()
6
+ "target": "es5",
7
+ "lib": ["dom", "dom.iterable", "esnext"],
8
+ "strict": true,
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "allowSyntheticDefaultImports": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "noFallthroughCasesInSwitch": true,
15
+
16
+ // These values are required and cannot be changed by the user
17
+ // Keep this in sync with the rollup config
18
+ "module": "esnext",
19
+ "moduleResolution": "node",
20
+ "resolveJsonModule": true,
21
+ "isolatedModules": true,
22
+ "jsx": "react-jsx",
23
+ "jsxImportSource": "preact"
24
+ },
25
+ "include": ["src"],
26
+ "exclude": ["src/_package"]
27
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'vite';
2
+
3
+ // https://vitejs.dev/config/
4
+ export default defineConfig({
5
+ // Add your own plugins here for thinkgs like eslint or typecheckers
6
+ plugins: [],
7
+ // @ts-ignore Missing test property
8
+ test: {
9
+ environment: 'jsdom'
10
+ }
11
+ });
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": ["react-app"]
3
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "base-component-react",
3
+ "description": "",
4
+ "version": "1.0.0",
5
+ "scripts": {
6
+ "lint": "eslint --max-warnings 0 --ext .js,.jsx,.ts,.tsx src",
7
+ "build": "tsc --noEmit && oc package .",
8
+ "test": "vitest"
9
+ },
10
+ "oc": {
11
+ "files": {
12
+ "data": "src/server.ts",
13
+ "template": {
14
+ "src": "src/App.tsx",
15
+ "type": "oc-template-react"
16
+ },
17
+ "static": [
18
+ "public"
19
+ ]
20
+ },
21
+ "parameters": {
22
+ "userId": {
23
+ "default": 1,
24
+ "description": "The user id from the user database",
25
+ "example": 1,
26
+ "mandatory": true,
27
+ "type": "number"
28
+ }
29
+ }
30
+ },
31
+ "dependencies": {},
32
+ "devDependencies": {
33
+ "@testing-library/react": "^14.0.0",
34
+ "@testing-library/user-event": "^14.4.3",
35
+ "@types/react": "^18.0.28",
36
+ "jsdom": "^21.1.1",
37
+ "oc-server": "*",
38
+ "oc-template-react-compiler": "6.0.0",
39
+ "react": "18.2.0",
40
+ "react-dom": "18.2.0",
41
+ "typescript": "5.1.3",
42
+ "vitest": "^0.32.2"
43
+ }
44
+ }
Binary file
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { describe, it, afterEach, beforeEach, expect, vi } from 'vitest';
3
+ import { render, screen } from '@testing-library/react';
4
+ import { DataProvider } from 'oc-template-react-compiler/dist/utils/useData';
5
+
6
+ import App from './App';
7
+
8
+ const getData = vi.fn();
9
+ const getSetting = vi.fn();
10
+
11
+ describe('App - Page', () => {
12
+ beforeEach(() => {
13
+ window.oc = { events: { on: vi.fn(), fire: vi.fn() } } as any;
14
+ });
15
+ afterEach(() => {
16
+ vi.restoreAllMocks();
17
+ });
18
+
19
+ it('Gets more data when clicking the button', () => {
20
+ getData.mockImplementationOnce(() => Promise.resolve({}));
21
+ getSetting.mockImplementationOnce(() => ({}));
22
+ render(
23
+ <DataProvider
24
+ value={{
25
+ firstName: 'firstName',
26
+ lastName: 'lastName',
27
+ userId: 0,
28
+ getData,
29
+ getSetting
30
+ }}
31
+ >
32
+ <App firstName="firstName" lastName="lastName" userId={0} />
33
+ </DataProvider>
34
+ );
35
+
36
+ const extraInfoButton = screen.getByText(/Get extra information/i);
37
+ expect(extraInfoButton).toBeTruthy();
38
+ });
39
+ });
@@ -0,0 +1,52 @@
1
+ import { useState } from 'react';
2
+ import { useData } from 'oc-template-react-compiler/dist/utils/useData';
3
+ import { serverClient } from 'oc-server';
4
+ import styles from './styles.css';
5
+ import logo from '../public/logo.png';
6
+ import type { AdditionalData, ClientProps } from './types';
7
+
8
+ interface AppProps extends ClientProps {
9
+ getMoreData?: boolean;
10
+ }
11
+
12
+ const App: React.FC<ClientProps> = () => {
13
+ const { firstName, lastName, userId, getData } = useData<AppProps>();
14
+ const [additionalData, setAdditionalData] = useState<AdditionalData | null>(null);
15
+ const [error, setError] = useState('');
16
+
17
+ const fetchMoreData = async () => {
18
+ setError('');
19
+ try {
20
+ const data = await serverClient.getMoreData({ userId });
21
+ setAdditionalData(data);
22
+ } catch (err) {
23
+ setError(String(err));
24
+ }
25
+ };
26
+
27
+ if (error) {
28
+ return <div>Something wrong happened!</div>;
29
+ }
30
+
31
+ return (
32
+ <div className={styles.container}>
33
+ <img width="50" height="50" src={logo} alt="Logo" />
34
+ <h1 style={{ margin: '0 0 20px 0' }}>
35
+ Hello, <span style={{ textDecoration: 'underline' }}>{firstName}</span> {lastName}
36
+ </h1>
37
+ {additionalData && (
38
+ <div className={styles.info}>
39
+ <div className={styles.block}>Age: {additionalData.age}</div>
40
+ <div className={styles.block}>
41
+ Hobbies: {additionalData.hobbies.map((x) => x.toLowerCase()).join(', ')}
42
+ </div>
43
+ </div>
44
+ )}
45
+ <button className={styles.button} onClick={fetchMoreData}>
46
+ Get extra information
47
+ </button>
48
+ </div>
49
+ );
50
+ };
51
+
52
+ export default App;
@@ -0,0 +1 @@
1
+ /// <reference types="oc-vite/client" />
@@ -0,0 +1,37 @@
1
+ import { Server } from 'oc-server';
2
+ import { AdditionalData, ClientProps, OcParameters } from './types';
3
+
4
+ const database = [
5
+ { name: 'John Doe', age: 34, hobbies: ['Swimming', 'Basketball'] },
6
+ { name: 'Jane Doe', age: 35, hobbies: ['Running', 'Rugby'] }
7
+ ];
8
+
9
+ async function getUser(userId: number) {
10
+ return database[userId];
11
+ }
12
+
13
+ export const server = new Server(async (params: OcParameters): Promise<ClientProps> => {
14
+ const { userId } = params;
15
+ const user = await getUser(userId);
16
+ const [firstName, lastName] = user.name.split(/\s+/);
17
+
18
+ return {
19
+ userId,
20
+ firstName,
21
+ lastName
22
+ };
23
+ }).action('getMoreData', async (params: OcParameters): Promise<AdditionalData> => {
24
+ const { userId } = params;
25
+ const user = await getUser(userId);
26
+
27
+ return {
28
+ age: user.age,
29
+ hobbies: user.hobbies
30
+ };
31
+ });
32
+
33
+ declare module 'oc-server' {
34
+ interface Register {
35
+ server: typeof server;
36
+ }
37
+ }
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1,30 @@
1
+ .container {
2
+ background-color: #3b246c;
3
+ color: #fff;
4
+ font-family: sans-serif;
5
+ padding: 40px;
6
+ }
7
+
8
+ .button {
9
+ background-color: #a97613;
10
+ border: none;
11
+ padding: 15px 32px;
12
+ text-align: center;
13
+ font-size: 16px;
14
+ text-decoration: none;
15
+ display: inline-block;
16
+ color: inherit;
17
+ cursor: pointer;
18
+ }
19
+
20
+ .info {
21
+ margin-bottom: 20px;
22
+ }
23
+
24
+ .block {
25
+ margin: 6px 0;
26
+ }
27
+
28
+ .button:hover {
29
+ background-color: #c79535;
30
+ }
@@ -0,0 +1,15 @@
1
+ export interface OcParameters {
2
+ userId: number;
3
+ getMoreData?: boolean;
4
+ }
5
+
6
+ export interface AdditionalData {
7
+ hobbies: string[];
8
+ age: number;
9
+ }
10
+
11
+ export interface ClientProps extends Partial<AdditionalData> {
12
+ userId: number;
13
+ firstName: string;
14
+ lastName: string;
15
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ // These are suggested values and will be set when not present in the
4
+ // tsconfig.json
5
+ // 'parsedValue' matches the output value from ts.parseJsonConfigFileContent()
6
+ "target": "es5",
7
+ "lib": ["dom", "dom.iterable", "esnext"],
8
+ "strict": true,
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "allowSyntheticDefaultImports": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "noFallthroughCasesInSwitch": true,
15
+
16
+ // These values are required and cannot be changed by the user
17
+ // Keep this in sync with the rollup config
18
+ "module": "ESNext",
19
+ "moduleResolution": "Node",
20
+ "resolveJsonModule": true,
21
+ "isolatedModules": true,
22
+ "jsx": "react-jsx"
23
+ },
24
+ "include": ["src"],
25
+ "exclude": ["src/_package"]
26
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'vite';
2
+
3
+ // https://vitejs.dev/config/
4
+ export default defineConfig({
5
+ // Add your own plugins here for thinkgs like eslint or typecheckers
6
+ plugins: [],
7
+ // @ts-ignore Missing test property
8
+ test: {
9
+ environment: 'jsdom'
10
+ }
11
+ });
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "base-component-solid",
3
+ "description": "",
4
+ "version": "1.0.0",
5
+ "scripts": {
6
+ "lint": "eslint --max-warnings 0 --ext .js,.jsx,.ts,.tsx src",
7
+ "build": "tsc --noEmit && oc package .",
8
+ "test": "vitest"
9
+ },
10
+ "oc": {
11
+ "files": {
12
+ "data": "src/server.ts",
13
+ "template": {
14
+ "src": "src/App.tsx",
15
+ "type": "oc-template-solid"
16
+ },
17
+ "static": [
18
+ "public"
19
+ ]
20
+ },
21
+ "parameters": {
22
+ "userId": {
23
+ "default": 1,
24
+ "description": "The user id from the user database",
25
+ "example": 1,
26
+ "mandatory": true,
27
+ "type": "number"
28
+ }
29
+ }
30
+ },
31
+ "devDependencies": {
32
+ "@solidjs/testing-library": "^0.7.0",
33
+ "@testing-library/jest-dom": "^5.16.5",
34
+ "@types/testing-library__jest-dom": "^5.14.5",
35
+ "jsdom": "^21.1.1",
36
+ "oc-server": "*",
37
+ "oc-template-solid-compiler": "0.4.0",
38
+ "solid-js": "^1.8.7",
39
+ "typescript": "5.0.2",
40
+ "vitest": "^0.29.7"
41
+ }
42
+ }
Binary file
@@ -0,0 +1,37 @@
1
+ import { describe, it, afterEach, beforeEach, expect, vi } from 'vitest';
2
+ import { render } from '@solidjs/testing-library';
3
+ import { DataProvider } from 'oc-template-solid-compiler/dist/utils/useData';
4
+
5
+ import App from './App';
6
+
7
+ const getData = vi.fn();
8
+ const getSetting = vi.fn();
9
+
10
+ describe('App - Page', () => {
11
+ beforeEach(() => {
12
+ window.oc = { events: { on: vi.fn(), fire: vi.fn() } } as any;
13
+ });
14
+ afterEach(() => {
15
+ vi.restoreAllMocks();
16
+ });
17
+
18
+ it('Gets more data when clicking the button', () => {
19
+ getData.mockImplementationOnce(() => Promise.resolve({}));
20
+ const { getByText } = render(() => (
21
+ <DataProvider
22
+ value={{
23
+ firstName: 'firstName',
24
+ lastName: 'lastName',
25
+ userId: 0,
26
+ getData,
27
+ getSetting
28
+ }}
29
+ >
30
+ <App />
31
+ </DataProvider>
32
+ ));
33
+
34
+ const extraInfoButton = getByText(/Get extra information/i);
35
+ expect(extraInfoButton).toBeTruthy();
36
+ });
37
+ });
@@ -0,0 +1,50 @@
1
+ import { createSignal, Show, For } from 'solid-js';
2
+ import { useData } from 'oc-template-solid-compiler/dist/utils/useData';
3
+ import { serverClient } from 'oc-server';
4
+ import styles from './styles.css';
5
+ import type { AdditionalData, ClientProps } from './types';
6
+
7
+ interface AppProps extends ClientProps {
8
+ getMoreData?: boolean;
9
+ }
10
+
11
+ const App = () => {
12
+ const { firstName, lastName, userId, getSetting } = useData<AppProps>();
13
+ const staticPath = getSetting('staticPath');
14
+ const [additionalData, setAdditionalData] = createSignal<AdditionalData | null>(null);
15
+ const [error, setError] = createSignal('');
16
+
17
+ async function handleClick() {
18
+ const data = await serverClient.getMoreData({ userId });
19
+
20
+ try {
21
+ setAdditionalData(data);
22
+ } catch (error) {
23
+ setError(String(error));
24
+ }
25
+ }
26
+
27
+ return (
28
+ <Show when={!error()} fallback={<div>Something wrong happened!</div>}>
29
+ <div class={styles.container}>
30
+ <img width="50" height="50" src={`${staticPath}/public/logo.png`} alt="Logo" />
31
+ <h1 style={{ margin: '0 0 20px 0' }}>
32
+ Hello, <span style={{ 'text-decoration': 'underline' }}>{firstName}</span> {lastName}
33
+ </h1>
34
+ <Show when={!!additionalData()}>
35
+ <div class={styles.info}>
36
+ <div class={styles.block}>Age: {additionalData()!.age}</div>
37
+ <div class={styles.block}>
38
+ Hobbies: <For each={additionalData()!.hobbies}>{(hobby) => <div>{hobby}</div>}</For>
39
+ </div>
40
+ </div>
41
+ </Show>
42
+ <button class={styles.button} onClick={handleClick}>
43
+ Get extra information
44
+ </button>
45
+ </div>
46
+ </Show>
47
+ );
48
+ };
49
+
50
+ export default App;
@@ -0,0 +1 @@
1
+ /// <reference types="oc-vite/client" />
@@ -0,0 +1,36 @@
1
+ import { Server } from 'oc-server';
2
+ import { ClientProps, OcParameters } from './types';
3
+
4
+ const database = [
5
+ { name: 'John Doe', age: 34, hobbies: ['Swimming', 'Basketball'] },
6
+ { name: 'Jane Doe', age: 35, hobbies: ['Running', 'Rugby'] }
7
+ ];
8
+
9
+ async function getUser(userId: number) {
10
+ return database[userId];
11
+ }
12
+
13
+ export const server = new Server(async (params: OcParameters): Promise<ClientProps> => {
14
+ const { userId } = params;
15
+ const user = await getUser(userId);
16
+
17
+ const [firstName, lastName] = user.name.split(/\s+/);
18
+ return {
19
+ userId,
20
+ firstName,
21
+ lastName
22
+ };
23
+ }).action('getMoreData', async (params: OcParameters) => {
24
+ const { userId } = params;
25
+ const user = await getUser(userId);
26
+ return {
27
+ age: user.age,
28
+ hobbies: user.hobbies
29
+ };
30
+ });
31
+
32
+ declare module 'oc-server' {
33
+ interface Register {
34
+ server: typeof server;
35
+ }
36
+ }
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1,30 @@
1
+ .container {
2
+ background-color: #3b246c;
3
+ color: #fff;
4
+ font-family: sans-serif;
5
+ padding: 40px;
6
+ }
7
+
8
+ .button {
9
+ background-color: #a97613;
10
+ border: none;
11
+ padding: 15px 32px;
12
+ text-align: center;
13
+ font-size: 16px;
14
+ text-decoration: none;
15
+ display: inline-block;
16
+ color: inherit;
17
+ cursor: pointer;
18
+ }
19
+
20
+ .info {
21
+ margin-bottom: 20px;
22
+ }
23
+
24
+ .block {
25
+ margin: 6px 0;
26
+ }
27
+
28
+ .button:hover {
29
+ background-color: #c79535;
30
+ }
@@ -0,0 +1,15 @@
1
+ export interface OcParameters {
2
+ userId: number;
3
+ getMoreData?: boolean;
4
+ }
5
+
6
+ export interface AdditionalData {
7
+ hobbies: string[];
8
+ age: number;
9
+ }
10
+
11
+ export interface ClientProps extends Partial<AdditionalData> {
12
+ userId: number;
13
+ firstName: string;
14
+ lastName: string;
15
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ // These are suggested values and will be set when not present in the
4
+ // tsconfig.json
5
+ // 'parsedValue' matches the output value from ts.parseJsonConfigFileContent()
6
+ "target": "es5",
7
+ "lib": ["dom", "dom.iterable", "esnext"],
8
+ "strict": true,
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "allowSyntheticDefaultImports": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "noFallthroughCasesInSwitch": true,
15
+
16
+ // These values are required and cannot be changed by the user
17
+ // Keep this in sync with the rollup config
18
+ "module": "esnext",
19
+ "moduleResolution": "node",
20
+ "resolveJsonModule": true,
21
+ "isolatedModules": true,
22
+ "jsx": "preserve",
23
+ "jsxImportSource": "solid-js"
24
+ },
25
+ "include": ["src"],
26
+ "exclude": ["src/_package"]
27
+ }
@@ -0,0 +1,20 @@
1
+ import { defineConfig } from 'vite';
2
+ import solidPlugin from 'vite-plugin-solid';
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig({
6
+ plugins: [solidPlugin()],
7
+ // @ts-ignore Missing test property
8
+ test: {
9
+ environment: 'jsdom',
10
+ globals: true,
11
+ transformMode: { web: [/\.[jt]sx?$/] },
12
+ setupFiles: ['node_modules/@testing-library/jest-dom/extend-expect.js'],
13
+ // otherwise, solid would be loaded twice:
14
+ deps: { registerNodeLoader: true },
15
+ // if you have few tests, try commenting one
16
+ // or both out to improve performance:
17
+ threads: false,
18
+ isolate: false,
19
+ },
20
+ });