chromium/chrome/test/data/webui/chromeos/print_preview_cros/destination_dropdown_controller_test.ts

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'chrome://os-print/js/destination_dropdown_controller.js';

import {PDF_DESTINATION} from 'chrome://os-print/js/data/destination_constants.js';
import {DESTINATION_MANAGER_ACTIVE_DESTINATION_CHANGED, DESTINATION_MANAGER_DESTINATIONS_CHANGED, DestinationManager} from 'chrome://os-print/js/data/destination_manager.js';
import {DestinationProviderComposite} from 'chrome://os-print/js/data/destination_provider_composite.js';
import {PRINT_REQUEST_FINISHED_EVENT, PrintTicketManager} from 'chrome://os-print/js/data/print_ticket_manager.js';
import {DESTINATION_DROPDOWN_DROPDOWN_DISABLED_CHANGED, DESTINATION_DROPDOWN_UPDATE_DESTINATIONS, DESTINATION_DROPDOWN_UPDATE_SELECTED_DESTINATION, DestinationDropdownController} from 'chrome://os-print/js/destination_dropdown_controller.js';
import {FakeDestinationProvider} from 'chrome://os-print/js/fakes/fake_destination_provider.js';
import {createCustomEvent} from 'chrome://os-print/js/utils/event_utils.js';
import {getDestinationProvider} from 'chrome://os-print/js/utils/mojo_data_providers.js';
import {EventTracker} from 'chrome://resources/js/event_tracker.js';
import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
import {MockController} from 'chrome://webui-test/chromeos/mock_controller.m.js';
import {MockTimer} from 'chrome://webui-test/mock_timer.js';
import {eventToPromise} from 'chrome://webui-test/test_util.js';

import {createTestDestination, resetDataManagersAndProviders, waitForInitialDestinationSet, waitForPrintTicketManagerInitialized, waitForSendPrintRequestFinished} from './test_utils.js';

suite('DestinationDropdownController', () => {
  let controller: DestinationDropdownController;
  let destinationManager: DestinationManager;
  let fakeDestinationProvider: FakeDestinationProvider;
  let printTicketManager: PrintTicketManager;
  let mockController: MockController;
  let eventTracker: EventTracker;
  let mockTimer: MockTimer;

  const testDelay = 1;

  setup(() => {
    mockController = new MockController();
    eventTracker = new EventTracker();
    mockTimer = new MockTimer();
    mockTimer.install();

    resetDataManagersAndProviders();
    fakeDestinationProvider =
        (getDestinationProvider() as DestinationProviderComposite)
            .fakeDestinationProvider;
    fakeDestinationProvider.setTestDelay(testDelay);
    destinationManager = DestinationManager.getInstance();
    printTicketManager = PrintTicketManager.getInstance();

    controller = new DestinationDropdownController(eventTracker);
  });

  teardown(() => {
    resetDataManagersAndProviders();
    eventTracker.removeAll();
    mockTimer.uninstall();
    mockController.reset();
  });

  // Verify controller can be constructed.
  test('controller is an event target', () => {
    assertTrue(
        controller instanceof EventTarget, 'Controller is an event target');
  });

  // Verify controller is listening to
  // DESTINATION_MANAGER_ACTIVE_DESTINATION_CHANGED event.
  test(
      'onDestinationManagerActiveDestinationChanged called on ' +
          DESTINATION_MANAGER_ACTIVE_DESTINATION_CHANGED,
      async () => {
        const onStateChangedFn = mockController.createFunctionMock(
            controller, 'onDestinationManagerActiveDestinationChanged');
        const stateChanged = eventToPromise(
            DESTINATION_MANAGER_ACTIVE_DESTINATION_CHANGED, destinationManager);
        onStateChangedFn.addExpectation();

        // Simulate event being fired.
        destinationManager.dispatchEvent(
            createCustomEvent(DESTINATION_MANAGER_ACTIVE_DESTINATION_CHANGED));
        await stateChanged;

        mockController.verifyMocks();
      });

  // Verify DESTINATION_MANAGER_ACTIVE_DESTINATION_CHANGED notifies the UI to
  // update.
  test(
      `${DESTINATION_MANAGER_ACTIVE_DESTINATION_CHANGED} triggers UI updates
      events`,
      async () => {
        let callCount = 0;
        let expectedCallCount = 0;
        controller.addEventListener(
            DESTINATION_DROPDOWN_UPDATE_SELECTED_DESTINATION, () => {
              ++callCount;
            });
        assertEquals(
            expectedCallCount, callCount,
            `${DESTINATION_DROPDOWN_UPDATE_SELECTED_DESTINATION} not emitted`);

        // Simulate event being fired.
        const activeChanged = eventToPromise(
            DESTINATION_MANAGER_ACTIVE_DESTINATION_CHANGED, destinationManager);
        destinationManager.dispatchEvent(
            createCustomEvent(DESTINATION_MANAGER_ACTIVE_DESTINATION_CHANGED));
        ++expectedCallCount;
        await activeChanged;

        assertEquals(
            expectedCallCount, callCount,
            `${DESTINATION_DROPDOWN_UPDATE_SELECTED_DESTINATION} emitted`);
      });

  // Verify getDestinations returns expected list of destinations.
  test('getDestinations returns expected list of destinations', () => {
    const getDestinationsFn = mockController.createFunctionMock(
        destinationManager, 'getDestinations');
    const expectedDestinations = [createTestDestination()];
    getDestinationsFn.returnValue = expectedDestinations;

    assertDeepEquals(expectedDestinations, controller.getDestinations());
  });

  // Verify controller is listening to
  // DESTINATION_MANAGER_DESTINATIONS_CHANGED event.
  test(
      'onDestinationManagerDestinationsChanged called on ' +
          DESTINATION_MANAGER_DESTINATIONS_CHANGED,
      async () => {
        const onDestinationsChangedFn = mockController.createFunctionMock(
            controller, 'onDestinationManagerDestinationsChanged');
        const destinationsChanged = eventToPromise(
            DESTINATION_MANAGER_DESTINATIONS_CHANGED, destinationManager);
        onDestinationsChangedFn.addExpectation();

        // Simulate event being fired.
        destinationManager.dispatchEvent(
            createCustomEvent(DESTINATION_MANAGER_DESTINATIONS_CHANGED));
        await destinationsChanged;

        mockController.verifyMocks();
      });

  // Verify DESTINATION_MANAGER_DESTINATIONS_CHANGED notifies the UI to
  // update.
  test(
      `${DESTINATION_MANAGER_DESTINATIONS_CHANGED} triggers UI updated event`,
      async () => {
        let callCount = 0;
        let expectedCallCount = 0;
        controller.addEventListener(
            DESTINATION_DROPDOWN_UPDATE_DESTINATIONS, () => {
              ++callCount;
            });
        assertEquals(
            expectedCallCount, callCount,
            `${DESTINATION_DROPDOWN_UPDATE_DESTINATIONS} not emitted`);

        // Simulate event being fired.
        const destinationsChanged = eventToPromise(
            DESTINATION_MANAGER_DESTINATIONS_CHANGED, destinationManager);
        destinationManager.dispatchEvent(
            createCustomEvent(DESTINATION_MANAGER_DESTINATIONS_CHANGED));
        ++expectedCallCount;
        await destinationsChanged;

        assertEquals(
            expectedCallCount, callCount,
            `${DESTINATION_DROPDOWN_UPDATE_DESTINATIONS} emitted`);
      });

  // Verify updateActiveDestination calls PrintTicketManager
  // setPrintTicketDestination with provided destination ID.
  test(
      'updateActiveDestination calls setPrintTicketDestination with ' +
          'destination ID',
      async () => {
        await waitForInitialDestinationSet(mockTimer, testDelay);
        const testDestination = createTestDestination();
        destinationManager.setDestinationForTesting(testDestination);
        const updatePrintTicketFn = mockController.createFunctionMock(
            printTicketManager, 'setPrintTicketDestination');
        updatePrintTicketFn.returnValue = true;
        updatePrintTicketFn.addExpectation(testDestination.id);
        controller.updateActiveDestination(testDestination.id);
        updatePrintTicketFn.verifyMock();
      });

  // Verify updateActiveDestination returns false if ID provided is already
  // active or an invalid ID. Also does not call setPrintTicketDestination.
  test(`updateActiveDestination returns false`, async () => {
    await waitForInitialDestinationSet(mockTimer, testDelay);

    assertFalse(
        controller.updateActiveDestination('unknownDestinationId'),
        'Update fails for unknown ID');
    assertFalse(
        controller.updateActiveDestination(PDF_DESTINATION.id),
        'Update fails for current ID');
  });

  // Verify controller emits DESTINATION_DROPDOWN_DROPDOWN_DISABLED_CHANGED
  // if disabled state should be re-evaluated, including:
  // - Destination manager has destinations loaded state.
  // - Print Request started or finished.
  test(
      'controller calls dispatchDropdownDisabled and emits event', async () => {
        const dispatchDropdownDisabledFn = mockController.createFunctionMock(
            controller, 'dispatchDropdownDisabled');
        // Dispatch called for destination manger twice for state changes.
        dispatchDropdownDisabledFn.addExpectation();
        dispatchDropdownDisabledFn.addExpectation();
        await waitForInitialDestinationSet(mockTimer, testDelay);

        // Dispatch called for print request started and print request finished.
        dispatchDropdownDisabledFn.addExpectation();
        dispatchDropdownDisabledFn.addExpectation();
        await waitForPrintTicketManagerInitialized();
        await waitForSendPrintRequestFinished(mockTimer, testDelay);

        // Dispatch should be called a total of 4 times.
        dispatchDropdownDisabledFn.verifyMock();

        // Reset mock and simulate request finished to verify emitted event.
        mockController.reset();
        const disabledChanged = eventToPromise(
            DESTINATION_DROPDOWN_DROPDOWN_DISABLED_CHANGED, controller);
        printTicketManager.dispatchEvent(
            createCustomEvent(PRINT_REQUEST_FINISHED_EVENT));
        await disabledChanged;
      });

  // Verify shouldDisableDropdown returns true if print request is in progress.
  test(
      'shouldDisableSelect returns true if print request in progress',
      async () => {
        // Initialize manager and force print request to be in progress.
        await waitForInitialDestinationSet(mockTimer, testDelay);
        await waitForPrintTicketManagerInitialized();
        const inProgressFn = mockController.createFunctionMock(
            printTicketManager, 'isPrintRequestInProgress');
        inProgressFn.returnValue = true;
        assertTrue(
            controller.shouldDisableDropdown(),
            'Disabled if print request is in progress');
      });

  // Verify shouldDisableDropdown returns true if destination manager is not
  // initialized.
  test(
      'shouldDisableSelect returns true if initial destinations are not ' +
          'loaded',
      async () => {
        // Initialize manager and force hasAnyDestinations to false.
        await waitForInitialDestinationSet(mockTimer, testDelay);
        await waitForPrintTicketManagerInitialized();
        const hasInitialDestFn = mockController.createFunctionMock(
            destinationManager, 'hasAnyDestinations');
        hasInitialDestFn.returnValue = false;
        assertTrue(
            controller.shouldDisableDropdown(),
            'Disabled if initial destinations are not loaded');
      });

  // Verify shouldDisableDropdown returns false if PrintTicketManager and
  // DestinationManager are in a state ready to receive destination changes.
  test('shouldDisableSelect returns false', async () => {
    await waitForInitialDestinationSet(mockTimer, testDelay);
    await waitForPrintTicketManagerInitialized();
    assertFalse(
        controller.shouldDisableDropdown(),
        'Enabled if active destination can be updated');
  });
});