Skip to content

Commit

Permalink
APP-5855 - Move JSON editors to PRIME (viamrobotics#553)
Browse files Browse the repository at this point in the history
  • Loading branch information
zaporter-work authored Aug 14, 2024
1 parent 5210f7b commit 9ebb0af
Show file tree
Hide file tree
Showing 29 changed files with 1,143 additions and 3 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ jobs:
name: npm-blocks-dist
path: packages/blocks/dist

- name: Upload npm @viamrobotics/prime-editor artifacts
uses: actions/upload-artifact@v3
with:
name: npm-editor-dist
path: packages/editor/dist

- name: Upload GitHub Pages artifacts
uses: actions/upload-pages-artifact@v1
with:
Expand Down Expand Up @@ -80,6 +86,12 @@ jobs:
name: npm-blocks-dist
path: packages/blocks/dist

- name: Download @viamrobotics/prime-editor npm artifacts
uses: actions/download-artifact@v3
with:
name: npm-editor-dist
path: packages/editor/dist

- name: Publish 🚀
run: pnpm publish -r --ignore-scripts
env:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ A collection of UI elements.

[`@viamrobotics/prime-core`][core] - Core components. Exported as Svelte components.

[`@viamrobotics/prime-editor`][editor] - Text-editing components over libraries like [codemirror](https://codemirror.net/). Exported as Svelte components.

[`@viamrobotics/prime-blocks`][blocks] - Large blocks of UI that often have dependencies like [Threlte][threlte]. Exported as Svelte components.

[legacy]: https://github.com/viamrobotics/prime/tree/main/packages/legacy
Expand Down
2 changes: 0 additions & 2 deletions packages/core/.npmrc

This file was deleted.

2 changes: 2 additions & 0 deletions packages/editor/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.svelte-kit
dist
41 changes: 41 additions & 0 deletions packages/editor/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use strict';

/** @type {import('node:path')} */
const path = require('node:path');

module.exports = {
root: true,
extends: ['@viamrobotics/eslint-config/svelte'],
parserOptions: {
project: ['./tsconfig.json'],
tsconfigRootDir: __dirname,
},
settings: {
tailwindcss: {
config: path.join(__dirname, 'tailwind.config.ts'),
},
},
env: {
browser: true,
node: true,
},
rules: {
// TODO(mc, 2024-01-03): move to base config?
'multiline-comment-style': 'off',
},
overrides: [
{
files: 'src/routes/**/*',
rules: {
'no-console': 'off',
'sonarjs/no-duplicate-string': 'off',
},
},
{
files: ['.eslintrc.cjs', '.prettierrc.cjs'],
rules: {
'@typescript-eslint/no-unsafe-assignment': 'off',
},
},
],
};
2 changes: 2 additions & 0 deletions packages/editor/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.svelte-kit
dist
12 changes: 12 additions & 0 deletions packages/editor/.prettierrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict';

/** @type {import('node:path')} */
const path = require('node:path');

/** @type {import('@viamrobotics/prettier-config/svelte')} */
const baseConfig = require('@viamrobotics/prettier-config/svelte');

module.exports = {
...baseConfig,
tailwindConfig: path.join(__dirname, 'tailwind.config.ts'),
};
39 changes: 39 additions & 0 deletions packages/editor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# `@viamrobotics/prime-editor`

## Getting started

`@viamrobotics/prime-editor` is a collection of Svelte components that wrap code-editing tools.

This primarily based around [Codemirror](https://codemirror.net/)

## Installation

Install use the README instructions at [@viamrobotics/prime-core](https://github.com/viamrobotics/prime/tree/main/packages/core) to install prime-core. Then install prime-editor using your package manager of choice:

```
pnpm add --save-dev @viamrobotics/prime-editor
```

<strong>These components require that ssr is disabled.</strong>
Ensure that you have a `+layout.ts` above all pages that use these components:

```js
export const prerender = false;
export const ssr = false;
```

## Usage

Once installed, you can use the components in your app:

```html
<script lang="ts">
import { JsonEditor } from '@viamrobotics/prime-editor';
</script>

<JsonEditor
initialValue={'{"a": "b"}'}
label="editor"
onChange={(e) => console.log(e)}
/>
```
93 changes: 93 additions & 0 deletions packages/editor/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{
"name": "@viamrobotics/prime-editor",
"version": "0.0.1",
"publishConfig": {
"access": "public"
},
"scripts": {
"prepare": "svelte-kit sync",
"dev": "vite dev",
"build": "vite build && pnpm run package",
"preview": "vite preview",
"package": "svelte-kit sync && svelte-package && publint",
"check": "concurrently -g pnpm:check-*",
"check-svelte": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check-lint": "pnpm run _prettier --check && pnpm run _eslint",
"format": "pnpm run _prettier --write",
"test": "svelte-kit sync && vitest run",
"test:watch": "vitest",
"_prettier": "prettier \"**/*.{js,cjs,ts,svelte,css,json,yml,yaml,md,mdx}\"",
"_eslint": "eslint \".*.cjs\" \"**/*.{js,cjs,ts,svelte}\""
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"svelte": "./dist/index.js"
}
},
"files": [
"dist",
"!__tests__"
],
"peerDependencies": {
"svelte": ">=4.0.0 <5",
"@codemirror/lang-json": ">=6 <7",
"@codemirror/merge": ">=6 <7",
"@codemirror/state": ">=6 <7",
"@codemirror/view": ">=6 <7",
"classnames": ">=2 <3",
"codemirror": ">=6 <7",
"lodash-es": ">=4 <5",
"@viamrobotics/prime-core": ">=0.0.141"
},
"devDependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/merge": "^6.6.1",
"@codemirror/state": "^6.3.1",
"@codemirror/view": "^6.22.0",
"classnames": "^2.3.2",
"codemirror": "^6.0.1",
"lodash-es": "^4.17.21",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.3",
"@sveltejs/package": "^2.2.3",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@testing-library/dom": "^9.3.3",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/svelte": "^4.1.0",
"@testing-library/user-event": "^14.5.1",
"@types/lodash-es": "^4.17.12",
"@types/prismjs": "^1.26.3",
"@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.15.0",
"@viamrobotics/eslint-config": "^0.3.0",
"@viamrobotics/prettier-config": "^0.3.4",
"@viamrobotics/prime-core": "workspace:^",
"@viamrobotics/typescript-config": "^0.1.0",
"autoprefixer": "^10.4.16",
"concurrently": "^8.2.2",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-sonarjs": "^0.23.0",
"eslint-plugin-svelte": "^2.35.1",
"eslint-plugin-tailwindcss": "^3.13.0",
"eslint-plugin-unicorn": "^49.0.0",
"jsdom": "^23.0.1",
"postcss": "^8.4.32",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"prettier-plugin-tailwindcss": "^0.5.9",
"publint": "^0.2.6",
"svelte": "^4.2.8",
"svelte-check": "^3.6.2",
"tailwindcss": "^3.3.7",
"tslib": "^2.6.2",
"type-fest": "^4.8.3",
"typescript": "^5.3.3",
"vite": "^5.0.10",
"vitest": "^1.1.0"
},
"svelte": "./dist/index.js",
"types": "./dist/index.d.ts",
"type": "module"
}
6 changes: 6 additions & 0 deletions packages/editor/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
16 changes: 16 additions & 0 deletions packages/editor/src/app.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* See https://kit.svelte.dev/docs/types#app
* for information about these interfaces
*/
declare global {
namespace App {
/*
* interface Error {}
* interface Locals {}
* interface PageData {}
* interface Platform {}
*/
}
}

export {};
18 changes: 18 additions & 0 deletions packages/editor/src/app.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link
rel="icon"
href="%sveltekit.assets%/favicon.png"
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div>%sveltekit.body%</div>
</body>
</html>
42 changes: 42 additions & 0 deletions packages/editor/src/lib/__tests__/json-diff.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { describe, expect, it } from 'vitest';
import type { ComponentProps } from 'svelte';
import { render, screen } from '@testing-library/svelte';

import Subject from '../json-diff.svelte';

const renderSubject = (props?: Partial<ComponentProps<Subject>>) => {
return render(Subject, {
labelPrefix: 'test',
beforeValue: '',
afterValue: '',
...props,
});
};

describe('json diff', () => {
it('should render both read-only editors with sorted JSON', async () => {
const beforeJson = { aa: 1, bb: 2 };
const afterJson = { aa: 1, cc: 4, bb: 3 };
renderSubject({
beforeValue: JSON.stringify(beforeJson, null, 2),
afterValue: JSON.stringify(afterJson, null, 2),
});
const beforeEditor = await screen.findByLabelText('test-before');
const afterEditor = await screen.findByLabelText('test-after');
expect(beforeEditor).toHaveTextContent(/\{ "aa": 1, "bb": 2\}/iu);
expect(afterEditor).toHaveTextContent(/\{ "aa": 1, "cc": 4, "bb": 3\}/iu);
});

it('should destroy the MergeView when the component is unmounted', () => {
const { unmount } = renderSubject();
// Check that the editor elements are in the DOM before destruction
expect(screen.queryAllByLabelText(/test-(?:before|after)/iu)).toHaveLength(
2
);
unmount();
// Check that the editor elements are no longer in the DOM
expect(screen.queryAllByLabelText(/test-(?:before|after)/iu)).toHaveLength(
0
);
});
});
68 changes: 68 additions & 0 deletions packages/editor/src/lib/__tests__/json-editor.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { describe, expect, it, vi } from 'vitest';
import type { ComponentProps } from 'svelte';
import { render, screen } from '@testing-library/svelte';
import userEvent from '@testing-library/user-event';

import Subject from '../json-editor.svelte';

const renderSubject = (props?: Partial<ComponentProps<Subject>>) => {
return render(Subject, {
label: 'test-editor',
initialValue: '',
debouncePeriodMS: 0,
...props,
});
};

describe('json editor', () => {
it('should render an editor with initial value', async () => {
const initialJson = { foo: 'bar', baz: 42 };
renderSubject({
initialValue: JSON.stringify(initialJson, null, 2),
});
const editor = await screen.findByLabelText('test-editor');
expect(editor).toHaveTextContent(/\{\s*"foo": "bar",\s*"baz": 42\s*\}/u);
});

it('should call onChange when content changes', async () => {
const onChange = vi.fn();
renderSubject({ onChange });
const editor = await screen.findByLabelText('test-editor');

await userEvent.type(editor, '{{"key": "value"}');

expect(onChange).toHaveBeenCalledWith('{"key": "value"}');
});

it('should set readonly mode correctly', async () => {
renderSubject({ readonly: true });
const editor = await screen.findByLabelText('test-editor');
expect(editor).toHaveAttribute('aria-readonly', 'true');
});

it('should show error state when isInvalid is true', async () => {
renderSubject({ isInvalid: true, errorMessageID: 'error-message' });
const editor = await screen.findByLabelText('test-editor');
expect(editor).toHaveAttribute('aria-invalid', 'true');
expect(editor).toHaveAttribute('aria-errormessage', 'error-message');
});

it('should update when initialValue prop changes', async () => {
const { rerender } = renderSubject({
initialValue: '{"initial": "value"}',
});
let editor = await screen.findByLabelText('test-editor');
expect(editor).toHaveTextContent(/"initial": "value"/u);

rerender({ initialValue: '{"updated": "value"}', label: 'test-editor' });
editor = await screen.findByLabelText('test-editor');
expect(editor).toHaveTextContent(/"updated": "value"/u);
});

it('should destroy the editor when the component is unmounted', () => {
const { unmount } = renderSubject();
expect(screen.queryByLabelText('test-editor')).toBeInTheDocument();
unmount();
expect(screen.queryByLabelText('test-editor')).not.toBeInTheDocument();
});
});
Loading

0 comments on commit 9ebb0af

Please sign in to comment.