Repository navigation
feat(event-display): load zipped version of event data in a generic way (#441) - #1040
Conversation
51336cd to
a7c4a27
Compare
|
🚀 Preview deployed: http://phoenix-pr-1040.surge.sh Built from 25ba0a8. |
|
Hi, I’m preparing a small test-only follow-up to #1040. At I have a local candidate based on your PR head that replaces the downloads, checks XML callback metadata and awaited ZIP delegation, and adds a real mixed JSON/XML archive test in the core helper. The focused tests pass with network calls blocked: 16 IO tests and 4 helper tests. I haven’t opened a separate PR. Would you prefer this as a follow-up after #1040, or coordinated within your PR? AI assistance was used for this investigation and test implementation. |
|
Hi! Thanks for catching this and putting together the local candidate. I've gone ahead and implemented your suggestions in my latest commit—replacing the network downloads with mock XML and adding the mixed archive test to the core helper. Let me know how it looks! |
|
Thanks for updating this. I checked The remaining follow-up can be limited to stronger assertions: invoking the XML callback and checking metadata, keeping ZIP parsing unresolved to verify the dialog stays open, and using the real AI assistance was used for source review and validation. |
|
Sharing the remaining test-only follow-up described above, based on The two-file patch adds XML callback/metadata assertions, verifies that the dialog stays open until ZIP parsing resolves, and exercises the real Validation on the complete resulting source tree: 638 tests passed, 7 skipped; lint, documentation coverage (100%) and the production web build passed. The 20 focused tests passed with outbound network calls blocked. Temporary fault probes confirmed the assertions catch omitted XML processing and premature dialog closure. The full checks were not repeated after adapting the commit to your updated head, because the complete source tree was identical to the validated candidate. An extra UI test TypeScript check has three errors also reproduced on the pinned clean main ( I have reviewed and understand the changes. AI assistance was used for investigation, test design, implementation and validation. Format-patch: test(app): strengthen event import completion and parser coverageSave the following block as From 0eae59d0788b0b3d5bd0fa91131c5853ba4b940c Mon Sep 17 00:00:00 2001
From: Dhairya Jangir <183471878+dhairyajangir@users.noreply.github.com>
Date: Sun, 4 Oct 2026 22:25:46 +0530
Subject: [PATCH] test(app): strengthen event import completion and parser
coverage
After the remote fixture removal, verify JiveXML callback metadata and
exact file delegation. Keep ZIP parsing pending to check that the dialog
closes only after completion, and exercise the real XML loader in the
mixed-archive helper test.
---
.../src/tests/helpers/zip.test.ts | 32 +++++---
.../io-options-dialog.component.test.ts | 74 +++++++++++++------
2 files changed, 73 insertions(+), 33 deletions(-)
diff --git a/packages/phoenix-event-display/src/tests/helpers/zip.test.ts b/packages/phoenix-event-display/src/tests/helpers/zip.test.ts
index 8baf393..80316a0 100644
--- a/packages/phoenix-event-display/src/tests/helpers/zip.test.ts
+++ b/packages/phoenix-event-display/src/tests/helpers/zip.test.ts
@@ -1,5 +1,9 @@
+/**
+ * @jest-environment jsdom
+ */
import JSZip from 'jszip';
import { readZipFile, loadEventsFromZip } from '../../helpers/zip';
+import { JiveXMLLoader } from '../../loaders/jivexml-loader';
describe('Zip Helper', () => {
it('should read zip file and return contents', async () => {
@@ -37,20 +41,24 @@ describe('Zip Helper', () => {
expect(events['JiveXML_1.xml']).toEqual({ EventXML: {} });
});
- it('should parse mixed JSON and XML archive', async () => {
+ it('should preserve JSON events and parse JiveXML metadata from a mixed zip', async () => {
+ const jsonEvent = { eventNumber: '21', Tracks: {} };
+ const xml =
+ '<Event eventNumber="42" runNumber="7" lumiBlock="3" dateTime="2026-01-01"/>';
const zip = new JSZip();
- zip.file('event1.json', '{"Event1": {"Tracks": {}}}');
- zip.file('JiveXML_1.xml', '<g></g>');
- const blob = await zip.generateAsync({ type: 'arraybuffer' });
+ zip.file('test_data.json', JSON.stringify({ jsonEvent }));
+ zip.file('test_data.xml', xml);
+ const archive = await zip.generateAsync({ type: 'arraybuffer' });
- const mockJiveLoader = {
- process: jest.fn(),
- getEventData: jest.fn().mockReturnValue({ EventXML: {} }),
- };
+ const events = await loadEventsFromZip(archive, new JiveXMLLoader());
- const events = await loadEventsFromZip(blob, mockJiveLoader as any);
- expect(events['Event1']).toBeDefined();
- expect(mockJiveLoader.process).toHaveBeenCalledWith('<g></g>');
- expect(events['JiveXML_1.xml']).toEqual({ EventXML: {} });
+ expect(Object.keys(events).sort()).toEqual(['jsonEvent', 'test_data.xml']);
+ expect(events.jsonEvent).toEqual(jsonEvent);
+ expect(events['test_data.xml']).toMatchObject({
+ eventNumber: '42',
+ runNumber: '7',
+ lumiBlock: '3',
+ time: '2026-01-01',
+ });
});
});
diff --git a/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/io-options/io-options-dialog/io-options-dialog.component.test.ts b/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/io-options/io-options-dialog/io-options-dialog.component.test.ts
index 5de15e2..42dda43 100644
--- a/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/io-options/io-options-dialog/io-options-dialog.component.test.ts
+++ b/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/io-options/io-options-dialog/io-options-dialog.component.test.ts
@@ -1,5 +1,4 @@
import JSZip from 'jszip';
-import fetch from 'node-fetch';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IOOptionsDialogComponent } from './io-options-dialog.component';
@@ -46,6 +45,10 @@ describe('IoOptionsDialogComponent', () => {
};
beforeEach(() => {
+ mockDialogRef.close.mockClear();
+ mockEventDisplayService.buildEventDataFromJSON.mockClear();
+ mockEventDisplayService.parseZipEventData.mockReset();
+
TestBed.configureTestingModule({
imports: [BrowserAnimationsModule, PhoenixUIModule],
providers: [
@@ -94,13 +97,35 @@ describe('IoOptionsDialogComponent', () => {
jest.spyOn(component, 'handleFileInput').mockImplementation(() => {});
});
- it('should handle JiveXML event data input', async () => {
- const mockXml = '<?xml version="1.0"?><Event></Event>';
- const files = mockFileList([
- new File([mockXml], 'testfile.xml', { type: 'text/xml' }),
- ]);
- component.handleJiveXMLDataInput(files);
- expect(component.handleFileInput).toHaveBeenCalled();
+ it('should parse JiveXML event data from the file input callback', () => {
+ const xml =
+ '<Event eventNumber="42" runNumber="7" lumiBlock="3" dateTime="2026-01-01"/>';
+ const file = new File([xml], 'testfile.xml', { type: 'text/xml' });
+
+ component.handleJiveXMLDataInput(mockFileList([file]));
+
+ expect(component.handleFileInput).toHaveBeenCalledWith(
+ file,
+ 'xml',
+ expect.any(Function),
+ );
+ const [, , parseJiveXML] = jest.mocked(component.handleFileInput).mock
+ .calls[0];
+ parseJiveXML(xml);
+
+ expect(
+ mockEventDisplayService.buildEventDataFromJSON,
+ ).toHaveBeenCalledTimes(1);
+ expect(
+ mockEventDisplayService.buildEventDataFromJSON,
+ ).toHaveBeenCalledWith(
+ expect.objectContaining({
+ eventNumber: '42',
+ runNumber: '7',
+ lumiBlock: '3',
+ time: '2026-01-01',
+ }),
+ );
});
describe('handleFileInput sync', () => {
@@ -155,19 +180,26 @@ describe('IoOptionsDialogComponent', () => {
});
});
- it('should handle zipped event data', async () => {
- const zip = new JSZip();
- zip.file('test_data.json', '{ "event": null }');
- const mockXml = '<?xml version="1.0"?><Event></Event>';
- zip.file('test_data.xml', mockXml);
- const zipBlob = await zip.generateAsync({ type: 'blob' });
- const files = mockFileList([
- new File([zipBlob], 'test_data.zip', { type: 'application/zip' }),
- ]);
- component.handleZipEventDataInput(files);
- // Need to await promises resolving inside the component
- await new Promise(process.nextTick);
- expect(mockEventDisplayService.parseZipEventData).toHaveBeenCalled();
+ it('should close after the selected ZIP file has finished parsing', async () => {
+ const file = new File([], 'test_data.zip', { type: 'application/zip' });
+ let completeParsing: () => void;
+ const parsing = new Promise<void>((resolve) => {
+ completeParsing = resolve;
+ });
+ mockEventDisplayService.parseZipEventData.mockReturnValueOnce(parsing);
+
+ const handling = component.handleZipEventDataInput(mockFileList([file]));
+
+ expect(mockEventDisplayService.parseZipEventData).toHaveBeenCalledTimes(1);
+ expect(mockEventDisplayService.parseZipEventData).toHaveBeenCalledWith(
+ file,
+ );
+ expect(mockDialogRef.close).not.toHaveBeenCalled();
+
+ completeParsing();
+ await handling;
+
+ expect(mockDialogRef.close).toHaveBeenCalledTimes(1);
});
it('should handle ig event data', async () => { |
| (fileName) => fileName.endsWith('.xml') || fileName.startsWith('JiveXML'), | ||
| ); | ||
|
|
||
| if (xmlFiles.length > 0 && jiveXMLLoader) { |
There was a problem hiding this comment.
jiveXMLLoader is optional, but if the client does not pass it in this case, it will now silently fail. Before the fallback was always to provide a JiveXMLLoader, so this seems like a regression... or at least, there should be a clear message?
There was a problem hiding this comment.
This is also missing from the tests I think (zip file contains xml but no decoder available).
There was a problem hiding this comment.
Good catch! I have restored the fallback new JiveXMLLoader() in event-display.ts when no loader is configured, and in zip.ts, if XML event files are present without a loader, it now logs a warning message to console.warn and infoLogger.add(..., 'Warning') rather than silently doing nothing.
There was a problem hiding this comment.
Added unit tests covering this case in packages/phoenix-event-display/src/tests/helpers/zip.test.ts to verify that a warning is logged when XML files are in the zip without a loader, as well as test coverage in event-display.test.ts verifying the fallback decoder in parseZipEventData.
| } catch (error) { | ||
| console.error('Error while reading zip', error); | ||
| this.eventDisplay.getInfoLogger().add('Could not read zip file', 'Error'); | ||
| this.notificationService.error('Could not read zip file.'); |
There was a problem hiding this comment.
This notification in case of error is lost now?
There was a problem hiding this comment.
Restored! handleZipEventDataInput now wraps parseZipEventData in a try/catch, shows this.notificationService.error('Could not read zip file.'), and returns early without closing the dialog so the user gets clear error feedback. EventDisplay.parseZipEventData also now re-throws after logging so callers can handle the failure. Added a unit test in io-options-dialog.component.test.ts to verify that the error notification is triggered and the dialog stays open on failure.
|
Hi @EdwardMoyse, Thank you for the review! I have addressed all feedback items in the latest commit:
All CI checks are passing and green. Please take a look when you have a chance! |
Description
This PR resolves Issue #441 by centralizing and making zip event loading generic across
phoenix-event-displayandphoenix-ui-components.Key Changes
loadEventsFromZiphelper inpackages/phoenix-event-display/src/helpers/zip.tsthat handles reading.zipfiles containing JSON or JiveXML event data.parseZipEventDataonEventDisplay: AddedEventDisplay.parseZipEventData(zipData)to simplify parsing zip event archives from both files and ArrayBuffers.URLOptionsManager.handleZipFileEventsandIOOptionsDialogComponent.handleZipEventDataInputto useparseZipEventData..zip/.phnxzip) inURLOptionsManagerwhentypeparameter is omitted in URL query params.packages/phoenix-event-display/src/tests/helpers/zip.test.ts.Closes #441