Skip to content

Commit

Permalink
[DLT-1110] Mapped Collection Endpoint Browse Tests (2/4) (#1207)
Browse files Browse the repository at this point in the history
* [DLT-1110] Add tests

* [DLT-1110] Update chai with mocha, attempt usage of mock-import

* [DLT-1110] Add mocha config, correct prettier

* [DLT-1110] Update tests, correct logic

* [DLT-1110] Update tests, add fixtures, update setup, lock in packages

* [DLT-1110] Update test
  • Loading branch information
AronPerez authored Jan 14, 2025
1 parent d445076 commit 22545fa
Show file tree
Hide file tree
Showing 8 changed files with 736 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/javascript-format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
# Step 3: Install Prettier and ESLint globally
- name: Install Prettier
run: |
npm install -g prettier
npm install -g prettier@3.4.2
# Step 4: Run Prettier to format code
- name: Run prettier
Expand Down
32 changes: 32 additions & 0 deletions web/.mocharc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Mocha configuration file.
*
* This configuration file sets up various options for running Mocha tests.
*
* @property {boolean} diff - Show diff on failure.
* @property {boolean} recursive - Include subdirectories.
* @property {boolean} exit - Force Mocha to quit after tests complete.
* @property {string[]} extension - File extensions to include.
* @property {string} package - Path to the package.json file.
* @property {string} reporter - Reporter to use.
* @property {number} timeout - Test-case timeout in milliseconds.
* @property {string} ui - User interface to use (e.g., BDD, TDD).
* @property {string[]} require - Modules to require before running tests.
* @property {string[]} watch-files - Files to watch for changes.
* @property {string[]} watch-ignore - Files to ignore when watching.
* @property {string[]} spec - Test files to run.
*/
module.exports = {
diff: true,
recursive: true,
exit: true,
extension: ["js"],
package: "./package.json",
reporter: "spec",
timeout: 2000,
ui: "bdd",
require: ["test/setup.js"],
"watch-files": ["test/**/*.js", "static/**/*.js"],
"watch-ignore": ["node_modules", "coverage"],
spec: ["test/**/*.test.js"],
};
14 changes: 9 additions & 5 deletions web/package.json.in
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"sanitize-html": "^2.11.0"
},
"scripts": {
"test": "mocha"
"test": "mocha --config .mocharc.cjs"
},
"repository": {
"type": "git",
Expand All @@ -36,10 +36,14 @@
},
"homepage": "https://github.com/ORNL/DataFed#readme",
"devDependencies": {
"chai": "^4",
"esm": "^3.2.25",
"mocha": "^10.8.2",
"pug": "^3.0.3"
"chai": "5.1.2",
"esm": "3.2.25",
"jsdom": "26.0.0",
"jsdom-global": "3.0.2",
"mocha": "11.0.1",
"prettier": "3.4.2",
"pug": "3.0.3",
"sinon": "19.0.2"
},
"type": "module"
}
80 changes: 80 additions & 0 deletions web/test/components/transfer/transfer-dialog-controller.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { expect, sinon } from "../../setup.js";
import { createMockServices, setupJQueryMocks } from "../../fixtures/transfer-fixtures.js";
import { TransferDialogController } from "../../../static/components/transfer/transfer-dialog-controller.js";
import { TransferEndpointManager } from "../../../static/components/transfer/transfer-endpoint-manager.js";
import { TransferMode } from "../../../static/models/transfer-model.js";
import { TransferUIManager } from "../../../static/components/transfer/transfer-ui-manager.js";

describe("TransferDialogController", () => {
let controller;
let mockCallback;
let mockServices;
let sandbox;

const TEST_MODE = TransferMode.TT_DATA_PUT;
const TEST_IDS = [{ id: 1 }, { id: 2 }];

beforeEach(() => {
sandbox = sinon.createSandbox();
mockCallback = sandbox.stub();
mockServices = createMockServices();
setupJQueryMocks(sandbox);

controller = new TransferDialogController(TEST_MODE, TEST_IDS, mockCallback, mockServices);
});

afterEach(() => {
sandbox.restore();
});

describe("constructor", () => {
it("should initialize with correct parameters and components", () => {
expect(controller.endpointManager).to.be.instanceOf(TransferEndpointManager);
expect(controller.uiManager).to.be.instanceOf(TransferUIManager);
expect(controller.ids).to.deep.equal(TEST_IDS);
expect(controller.callback).to.equal(mockCallback);

expect(controller.endpointManager.api).to.equal(mockServices.api);
expect(controller.uiManager.api).to.equal(mockServices.api);
expect(controller.endpointManager.dialogs).to.equal(mockServices.dialogs);
expect(controller.uiManager.dialogs).to.equal(mockServices.dialogs);
});

it("should initialize with default services if none provided", () => {
const defaultController = new TransferDialogController(
TEST_MODE,
TEST_IDS,
mockCallback,
);
expect(defaultController.services).to.have.property("dialogs");
expect(defaultController.services).to.have.property("api");
});
});

describe("show", () => {
it("should successfully show the transfer dialog", async () => {
sandbox.stub(controller.uiManager, "initializeComponents");
sandbox.stub(controller.uiManager, "attachMatchesHandler");
sandbox.stub(controller.uiManager, "showDialog");

await controller.show();

expect(controller.uiManager.initializeComponents.called).to.be.true;
expect(controller.uiManager.attachMatchesHandler.called).to.be.true;
expect(controller.endpointManager.initialized).to.be.true;
expect(controller.uiManager.showDialog.called).to.be.true;
});

it("should handle errors gracefully", async () => {
sandbox
.stub(controller.uiManager, "initializeComponents")
.throws(new Error("Test error"));

await controller.show();

expect(
mockServices.dialogs.dlgAlert.calledWith("Error", "Failed to open transfer dialog"),
).to.be.true;
});
});
});
197 changes: 197 additions & 0 deletions web/test/components/transfer/transfer-endpoint-manager.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { expect, sinon } from "../../setup.js";
import { createMockServices, setupJQueryMocks } from "../../fixtures/transfer-fixtures.js";
import { TransferEndpointManager } from "../../../static/components/transfer/transfer-endpoint-manager.js";

describe("TransferEndpointManager", () => {
let jQueryStub;
let manager;
let mockController;
let mockServices;
let sandbox;

beforeEach(() => {
sandbox = sinon.createSandbox();
mockServices = createMockServices();
jQueryStub = setupJQueryMocks(sandbox);

document.body.innerHTML = `
<div id="frame">
<textarea id="path"></textarea>
<select id="matches"></select>
</div>
`;

mockController = {
uiManager: {
state: {
frame: $("#frame"),
endpointOk: false,
},
updateEndpoint: sandbox.stub().returnsThis(),
updateButtonStates: sandbox.stub().returnsThis(),
},
};

manager = new TransferEndpointManager(mockController, mockServices);
manager.initialized = true;
manager.controller = mockController;
});

afterEach(() => {
sandbox.restore();
});

describe("searchEndpoint", () => {
beforeEach(() => {
manager.currentSearchToken = "test-token";
});

it("should update UI on successful direct endpoint match", () => {
const mockData = { name: "test-endpoint" };
mockServices.api.epView.callsFake((endpoint, callback) => callback(true, mockData));
manager.searchEndpoint("test-endpoint", "test-token");

expect(manager.controller.uiManager.updateEndpoint.calledWith(mockData)).to.be.true;
expect(manager.controller.uiManager.state.endpointOk).to.be.true;
expect(manager.controller.uiManager.updateButtonStates.called).to.be.true;
});

it("should fall back to autocomplete when no direct match found", () => {
mockServices.api.epView.callsFake((endpoint, callback) =>
callback(true, { code: "ERROR" }),
);
const searchAutocompleteSpy = sandbox.spy(manager, "searchEndpointAutocomplete");

manager.searchEndpoint("test-endpoint", "test-token");

expect(searchAutocompleteSpy.calledWith("test-endpoint", "test-token")).to.be.true;
});

it("should handle API errors", () => {
mockServices.api.epView.throws(new Error("API Error"));

manager.searchEndpoint("test-endpoint", "test-token");

expect(mockServices.dialogs.dlgAlert.calledWith("Globus Error", sinon.match.any)).to.be
.true;
});
});

describe("searchEndpointAutocomplete", () => {
beforeEach(() => {
manager.currentSearchToken = "test-token";
});

it("should update matches list with autocomplete results", () => {
const updateMatchesListSpy = sandbox.spy(manager, "updateMatchesList");
const mockData = {
DATA: [
{ id: "1", canonical_name: "endpoint1" },
{ id: "2", canonical_name: "endpoint2" },
],
};
mockServices.api.epAutocomplete.callsFake((endpoint, callback) =>
callback(true, mockData),
);

manager.searchEndpointAutocomplete("test", "test-token");

expect(updateMatchesListSpy.calledWith(mockData.DATA)).to.be.true;
expect(manager.endpointManagerList).to.deep.equal(mockData.DATA);
expect(jQueryStub.html.called).to.be.true;
expect(jQueryStub.prop.calledWith("disabled", false)).to.be.true;
});

it("should handle no matches case", () => {
const updateMatchesListSpy = sandbox.spy(manager, "updateMatchesList");
mockServices.api.epAutocomplete.callsFake((endpoint, callback) =>
callback(true, { DATA: [] }),
);
const consoleWarnStub = sinon.stub(console, "warn");

manager.searchEndpointAutocomplete("test", "test-token");

expect(manager.endpointManagerList).to.be.null;
expect(updateMatchesListSpy.calledWith([])).to.be.true;
expect(jQueryStub.html.calledWith("<option disabled selected>No Matches</option>")).to
.be.true;
expect(jQueryStub.prop.calledWith("disabled", true)).to.be.true;
expect(consoleWarnStub.calledWith("No matches found")).to.be.true;
});

it("should handle error responses", () => {
mockServices.api.epAutocomplete.callsFake((endpoint, callback) =>
callback(true, { code: "ERROR", DATA: [] }),
);

manager.searchEndpointAutocomplete("test", "test-token");

expect(mockServices.dialogs.dlgAlert.calledWith("Globus Error", "ERROR")).to.be.true;
});
});

describe("handlePathInput", () => {
beforeEach(() => {
manager.currentSearchToken = "test-token";
});

it("should process valid path input", () => {
jQueryStub.val.returns("endpoint/path");
const searchEndpointSpy = sandbox.spy(manager, "searchEndpoint");

manager.handlePathInput("test-token");

expect(searchEndpointSpy.calledWith("endpoint", "test-token")).to.be.true;
});

it("should handle empty path input", () => {
jQueryStub.val.returns("");

manager.handlePathInput("test-token");

expect(manager.endpointManagerList).to.be.null;
expect(manager.controller.uiManager.updateButtonStates.called).to.be.true;
});

it("should ignore stale requests", () => {
jQueryStub.val.returns("endpoint/path");
manager.currentSearchToken = "different-token";
const searchEndpointSpy = sandbox.spy(manager, "searchEndpoint");

manager.handlePathInput("test-token");

expect(searchEndpointSpy.called).to.be.false;
});

it("should handle uninitialized state", () => {
manager.initialized = false;
const handlePathInputSpy = sandbox.spy(manager, "handlePathInput");

manager.handlePathInput("test-token");

expect(handlePathInputSpy.calledOnce).to.be.true;
});
});

describe("updateMatchesList", () => {
it("should update matches list with endpoints", () => {
const endpoints = [
{ id: "1", name: "endpoint1" },
{ id: "2", name: "endpoint2" },
];

manager.updateMatchesList(endpoints);

expect(jQueryStub.html.called).to.be.true;
expect(jQueryStub.prop.calledWith("disabled", false)).to.be.true;
});

it("should handle empty endpoints list", () => {
manager.updateMatchesList([]);

expect(jQueryStub.html.calledWith("<option disabled selected>No Matches</option>")).to
.be.true;
expect(jQueryStub.prop.calledWith("disabled", true)).to.be.true;
});
});
});
Loading

0 comments on commit 22545fa

Please sign in to comment.