Skip to content

feat(event-display): load zipped version of event data in a generic way (#441) - #1040

Open
DashratRajpurohit wants to merge 4 commits into
HSF:mainfrom
DashratRajpurohit:feat/441-generic-zip-event-data
Open

DashratRajpurohit wants to merge 4 commits into
HSF:mainfrom
DashratRajpurohit:feat/441-generic-zip-event-data

Conversation

@DashratRajpurohit

Copy link
Copy Markdown
Contributor

Description

This PR resolves Issue #441 by centralizing and making zip event loading generic across phoenix-event-display and phoenix-ui-components.

Key Changes

  1. Centralized Zip Event Extraction: Added loadEventsFromZip helper in packages/phoenix-event-display/src/helpers/zip.ts that handles reading .zip files containing JSON or JiveXML event data.
  2. Exposed parseZipEventData on EventDisplay: Added EventDisplay.parseZipEventData(zipData) to simplify parsing zip event archives from both files and ArrayBuffers.
  3. URL & IO Dialog Refactoring:
    • Refactored URLOptionsManager.handleZipFileEvents and IOOptionsDialogComponent.handleZipEventDataInput to use parseZipEventData.
    • Added automatic extension detection (.zip / .phnxzip) in URLOptionsManager when type parameter is omitted in URL query params.
  4. Unit Tests: Added comprehensive unit test coverage in packages/phoenix-event-display/src/tests/helpers/zip.test.ts.

Closes #441

@DashratRajpurohit
DashratRajpurohit force-pushed the feat/441-generic-zip-event-data branch from 51336cd to a7c4a27 Compare September 27, 2026 13:53
@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

🚀 Preview deployed: http://phoenix-pr-1040.surge.sh

Built from c0799b9.

@dhairyajangir

Copy link
Copy Markdown

Hi, I’m preparing a small test-only follow-up to #1040. At cf8ae6ec, the IO-dialog JiveXML and ZIP tests still download the large XML example. With network calls blocked, those two cases fail while the other 14 pass.

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.

@DashratRajpurohit

Copy link
Copy Markdown
Contributor Author

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!

@dhairyajangir

Copy link
Copy Markdown

Thanks for updating this. I checked c0799b9: both focused suites pass with network calls blocked (16 IO tests and 4 helper tests), and yarn lint passes. The fixture downloads are gone.

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 JiveXMLLoader in the mixed-archive case. The current tests cover delegation and mixed-entry dispatch; these would add callback/parser and completion-timing coverage.

AI assistance was used for source review and validation.

@dhairyajangir

Copy link
Copy Markdown

Sharing the remaining test-only follow-up described above, based on c0799b9c2081a009f4b69ebb81b3e5a24c5e6217, for use within this PR if useful. Your fixture-removal changes are already included in the base.

The two-file patch adds XML callback/metadata assertions, verifies that the dialog stays open until ZIP parsing resolves, and exercises the real JiveXMLLoader with a mixed JSON/XML archive. It also removes the unused node-fetch import.

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 (beafa893); it is not a passing check. Browser behavior and integration with newer main have not been validated.

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 coverage

Save the following block as phoenix-import-assertions.patch and use git am phoenix-import-assertions.patch on a checkout at the base above. This preserves the patch's author information.

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 () => {

This branch was successfully deployed

1 active deployment
pull-request — c0799b9c Deployed Oct 4, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Be able to load zipped version of event/geometry files in a generic way

2 participants