This is an automated email from the ASF dual-hosted git repository.
tbonelee pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/master by this push:
new c8c0d3d255 [ZEPPELIN-6685] Resolve the app's imports in the shell unit
test setup
c8c0d3d255 is described below
commit c8c0d3d255f7edf017d312fe05d6d26ddd2deaa1
Author: YONGJAE LEE (이용재) <[email protected]>
AuthorDate: Fri Sep 4 23:42:58 2026 +0900
[ZEPPELIN-6685] Resolve the app's imports in the shell unit test setup
### What is this PR for?
`vitest.shell.config.mts` declares no `resolve.alias`, and Vite does not
read the `paths` block in `tsconfig.base.json`, so a spec cannot reach
application source:
```
Failed to resolve import "<at>zeppelin/interfaces" from
"src/app/services/array-ordering.service.ts"
```
The failure is in the source, not the spec: most of `src/` imports
`<at>zeppelin/*` itself, so a relative import does not avoid it.
`monaco-editor` does not resolve either. It publishes no `main` and no
`exports`
([monaco-editor#4848](https://github.com/microsoft/monaco-editor/issues/4848));
`module` names `editor.main`, which boots the full editor and fails under
jsdom. Eleven files import it, two behind the `<at>zeppelin/services` barrel.
This adds the alias block, a spec that exercises it, and a note in
`AGENTS.md`. Two entries differ from `tsconfig.base.json`:
* `<at>zeppelin/sdk` and `<at>zeppelin/visualization` resolve to library
source, not `dist/`, so a unit run does not wait on a build.
`projects/zeppelin-react/vitest.config.mts` already does this for the SDK.
* `monaco-editor` resolves to `esm/vs/editor/editor.api.js`.
### What type of PR is it?
Improvement
### Todos
None
### What is the Jira issue?
ZEPPELIN-6685
### How should this be tested?
`npm run test:shell` goes from 29 tests to 37. The new spec covers
`ArrayOrderingService`: trash folder last, blank title falls back to `Note
<id>`, folders before notes, same-kind nodes by display name.
It imports through the `<at>zeppelin/services` barrel so the chain reaches
monaco and `<at>zeppelin/sdk`. Pointing any of `monaco-editor`,
`<at>zeppelin/sdk` or the catch-all at a nonexistent path fails it. Nothing
imports the `sdk/*` or `visualization/*` subpaths today, so those two are
uncovered.
`ng build --configuration production` and `ng lint` both pass unchanged:
only Vitest reads the config, and `src/tsconfig.json` excludes specs from the
build.
### Screenshots (if appropriate)
N/A
### Questions:
* Does the license files need to update? No
* Is there breaking changes for older versions? No
* Does this needs documentation? No
Closes #5439 from voidmatcha/vitest-shell-alias.
Signed-off-by: ChanHo Lee <[email protected]>
---
zeppelin-web-angular/AGENTS.md | 4 ++
.../app/services/array-ordering.service.spec.ts | 77 ++++++++++++++++++++++
zeppelin-web-angular/vitest.shell.config.mts | 33 ++++++++++
3 files changed, 114 insertions(+)
diff --git a/zeppelin-web-angular/AGENTS.md b/zeppelin-web-angular/AGENTS.md
index ddec79bf2b..92b08f597a 100644
--- a/zeppelin-web-angular/AGENTS.md
+++ b/zeppelin-web-angular/AGENTS.md
@@ -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 [...]
+
## 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.
diff --git
a/zeppelin-web-angular/src/app/services/array-ordering.service.spec.ts
b/zeppelin-web-angular/src/app/services/array-ordering.service.spec.ts
new file mode 100644
index 0000000000..0508814f40
--- /dev/null
+++ b/zeppelin-web-angular/src/app/services/array-ordering.service.spec.ts
@@ -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);
+ });
+ });
+});
diff --git a/zeppelin-web-angular/vitest.shell.config.mts
b/zeppelin-web-angular/vitest.shell.config.mts
index da9eab60a6..c6b35960aa 100644
--- a/zeppelin-web-angular/vitest.shell.config.mts
+++ b/zeppelin-web-angular/vitest.shell.config.mts
@@ -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".