Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions zeppelin-web-angular/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ A spec with no assertion, or one whose assertion sits inside an `if`, passes by

The e2e suite gets the same protection from `eslint-plugin-playwright`.

## monaco-editor and path aliases in specs

`vitest.shell.config.mts` mirrors the `paths` block in `tsconfig.base.json`, which Vite does not read; add an alias there when you add a path. Eleven files under `src/` import `monaco-editor`, two behind the `@zeppelin/services` barrel, so a spec that reaches the editor or notebook area loads it: a few seconds on first import and a `marked.umd.js.map` sourcemap warning, both monaco's, not ours. Mock it with `vi.mock('monaco-editor', ...)` when the spec only needs to assert the editor was called. The runner resolves monaco to `editor.api`, which skips the language registrations `editor.main` performs, so do not assert that a built-in language is registered.

## Determinism

No clock, no randomness, no network. A spec that reads `Date.now()` or fetches will eventually fail for reasons unrelated to the code under test.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { beforeEach, describe, expect, it } from 'vitest';
import { NodeItem } from '@zeppelin/interfaces';
// Through the barrel, not the neighbouring file:
// that is what walks the alias chain to monaco and @zeppelin/sdk,
// so this spec fails if either stops resolving.
import { ArrayOrderingService } from '@zeppelin/services';

const TRASH_FOLDER_ID = '~Trash';

const note = (id: string, title: string, children?: NodeItem[]): NodeItem => ({ id, title, children }) as NodeItem;

describe('ArrayOrderingService', () => {
let service: ArrayOrderingService;

beforeEach(() => {
service = new ArrayOrderingService(TRASH_FOLDER_ID);
});

describe('getNoteName', () => {
it('returns the title when it has one', () => {
expect(service.getNoteName(note('a1', 'My note'))).toBe('My note');
});

it('falls back to the id when the title is blank', () => {
expect(service.getNoteName(note('a1', ' '))).toBe('Note a1');
});
});

describe('noteListOrdering', () => {
it('sorts the trash folder last by returning the highest code point', () => {
expect(service.noteListOrdering(note(TRASH_FOLDER_ID, 'Trash'))).toBe('￿');
});

it('orders every other node by its display name', () => {
expect(service.noteListOrdering(note('a1', 'My note'))).toBe('My note');
});
});

describe('noteComparator', () => {
it('puts the trash folder after anything else, whichever side it is on', () => {
const trash = note(TRASH_FOLDER_ID, 'Trash');
const other = note('a1', 'My note');

expect(service.noteComparator(trash, other)).toBe(1);
expect(service.noteComparator(other, trash)).toBe(-1);
});

it('puts folders before notes', () => {
const folder = note('f1', 'Folder', []);
const leaf = note('a1', 'Note');

expect(service.noteComparator(leaf, folder)).toBe(1);
expect(service.noteComparator(folder, leaf)).toBe(-1);
});

it('compares two nodes of the same kind by display name', () => {
expect(service.noteComparator(note('a1', 'Alpha'), note('a2', 'Beta'))).toBeLessThan(0);
});

it('uses the id fallback when a title is blank', () => {
// 'Note a1' sorts before 'Zebra', which the raw empty title would not.
expect(service.noteComparator(note('a1', ''), note('a2', 'Zebra'))).toBeLessThan(0);
});
});
});
33 changes: 33 additions & 0 deletions zeppelin-web-angular/vitest.shell.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,42 @@
*/

// vite is pinned in package.json: the React remote keeps its own lockfile and drifted to a different minor.
import { fileURLToPath } from 'node:url';
import { defineConfig } from 'vitest/config';

export default defineConfig({
// Mirrors the `paths` block in tsconfig.base.json, which Vite does not read.
// The two library aliases resolve to source, not `dist/`, so a unit run does not wait on a build.
resolve: {
alias: [
// monaco-editor ships no `main` and no `exports` (microsoft/monaco-editor#4848).
// `module` names editor.main, which boots the full editor and dies in jsdom.
// 0.55 restores resolution but still points there, so revisit rather than drop.
{
find: /^monaco-editor$/,
replacement: fileURLToPath(new URL('./node_modules/monaco-editor/esm/vs/editor/editor.api.js', import.meta.url))
},
{ find: /^@zeppelin\/sdk$/, replacement: fileURLToPath(new URL('./projects/zeppelin-sdk/src', import.meta.url)) },
{
find: /^@zeppelin\/sdk\/(.*)$/,
replacement: `${fileURLToPath(new URL('./projects/zeppelin-sdk/src', import.meta.url))}/$1`
},
{
find: /^@zeppelin\/visualization$/,
replacement: fileURLToPath(new URL('./projects/zeppelin-visualization/src', import.meta.url))
},
{
find: /^@zeppelin\/visualization\/(.*)$/,
replacement: `${fileURLToPath(new URL('./projects/zeppelin-visualization/src', import.meta.url))}/$1`
},
// `@zeppelin/*` falls back to src/environments in tsconfig; Vite aliases do not.
{
find: /^@zeppelin\/environment$/,
replacement: fileURLToPath(new URL('./src/environments/environment.ts', import.meta.url))
},
{ find: /^@zeppelin\/(.*)$/, replacement: `${fileURLToPath(new URL('./src/app', import.meta.url))}/$1` }
]
},
// oxc does not apply the decorator options from tsconfig.base.json to specs,
// which src/tsconfig.json excludes. Undeclared, a decorated spec fails to
// parse with "Invalid or unexpected token".
Expand Down
Loading