/* eslint-disable no-console */
import React from 'react';
import * as apollo from '@apollo/client';
import {
    act,
    fireEvent,
    screen,
    waitFor,
    within,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Segment } from '@theorchard/suite-frontend';
import { renderInAppContext } from '@theorchard/suite-testing';
import { createMemoryHistory, MemoryHistory } from 'history';
import { waitForLoaded } from 'lib/test/helpers';
import { Router } from 'react-router-dom';
import { FEATURE_FLAGS } from 'src/constants';
import * as deleteComposition from 'src/data/mutations/deleteComposition/deleteComposition';
import * as compositions from 'src/data/queries/compositions/compositions';
import * as orchardLabelById from 'src/data/queries/orchardLabel/orchardLabelById';
import * as orchardLabels from 'src/data/queries/orchardLabels/orchardLabels';
import * as songWritersSearchSimple from 'src/data/queries/songWritersSearchSimple/songWritersSearchSimple';
import { CompanyBrandName } from 'src/data/schemaTypes';
import * as applicationContext from 'src/utils/applicationContext';
import {
    compositionData,
    compositionDataLoading,
    generateNMockCompositions,
    labelMock,
    labelsMock,
    songWritersResponse,
} from '../mocks';
import SongListPage from '../songListPage';

describe('<SongListPage>', () => {
    const originalWarn = console.error.bind(console.error);

    const defaultRowsPerPage = 25;
    const defaultRouteParams = {
        tab: 'submitted',
        page: '1',
    };

    let songWritersFn: jest.SpyInstance;
    let compositionsFn: jest.SpyInstance;
    let memoryHistory: MemoryHistory;
    const deleteMutationSpy = jest.fn();

    // `renderInAppContext` defaults to an employee identity with no features,
    // i.e. the flag is off unless a test opts in.
    const adminUxFlagOn = {
        features: { [FEATURE_FLAGS.ADMIN_UX_IMPROVEMENTS]: true },
    };

    const lastCompositionsFilter = () =>
        compositionsFn.mock.calls[compositionsFn.mock.calls.length - 1][0];

    const renderComponent = (
        tab = defaultRouteParams.tab,
        page = defaultRouteParams.page,
        contextPropsOverrides = {}
    ) => {
        const contextProps = {
            ...contextPropsOverrides,
            pathname: `/songs/${tab}`,
            search: `?page=${page}`,
        };

        memoryHistory = createMemoryHistory();

        return renderInAppContext(<SongListPage />, contextProps);
    };

    beforeAll(() => {
        // To avoid the warning "Can't perform a React state update on an unmounted component"
        console.error = (msg: string) => {
            if (
                !msg.includes(
                    "Can't perform a React state update on an unmounted component"
                )
            )
                originalWarn(msg);
        };
    });

    const renderComponentWithHistory = (
        tab = defaultRouteParams.tab,
        page = defaultRouteParams.page,
        contextPropsOverrides = {}
    ) => {
        const contextProps = {
            ...contextPropsOverrides,
            pathname: `/songs/${tab}`,
            search: `?page=${page}`,
        };

        memoryHistory = createMemoryHistory();

        return renderInAppContext(
            // eslint-disable-next-line
            // @ts-ignore
            <Router history={memoryHistory}>
                <SongListPage />
            </Router>,
            contextProps
        );
    };

    beforeEach(() => {
        jest.spyOn(Segment, 'trackEvent');

        compositionsFn = jest
            .spyOn(compositions, 'useCompositions')
            .mockReturnValue({
                error: undefined,
                loading: false,
                data: compositionData,
            });

        songWritersFn = jest
            .spyOn(
                songWritersSearchSimple,
                'useLazyPublishingSongWritersSimpleSearch'
            )
            .mockReturnValue([
                jest.fn().mockResolvedValue({
                    error: undefined,
                    loading: false,
                    data: songWritersResponse,
                }),
                {
                    data: songWritersResponse,
                } as apollo.QueryResult,
            ]);

        jest.spyOn(orchardLabels, 'useOrchardLabels').mockReturnValue(
            labelsMock
        );
        jest.spyOn(
            orchardLabelById,
            'useOrchardLabelByIdQuery'
        ).mockReturnValue({
            data: labelMock.data[0],
            loading: false,
            error: undefined,
        });
        jest.spyOn(deleteComposition, 'useDeleteComposition').mockReturnValue(
            deleteMutationSpy
        );
    });

    afterEach(() => {
        jest.restoreAllMocks();
    });

    afterAll(() => {
        console.warn = originalWarn;
    });

    test('defaults to the submitted songs tab', async () => {
        const { container } = renderComponent();
        await act(async () => {
            await waitForLoaded();
        });
        const activeTab =
            container.querySelector<HTMLAnchorElement>('a.nav-link.active');
        expect(activeTab?.href).toMatch('/songs/submitted?page=1');
    });

    test('adds the decimals correctly', async () => {
        renderComponent();
        await act(async () => {
            await waitForLoaded();
        });
        expect(screen.getByText('51.2% Ownership')).toBeTruthy();
    });

    describe('Date columns with the flag on', () => {
        test.each(['submitted', 'drafts'])(
            'has created, submitted and modified date columns on the %s tab',
            async tab => {
                renderComponent(tab, defaultRouteParams.page, {
                    identity: adminUxFlagOn,
                });
                await act(async () => {
                    await waitForLoaded();
                });

                expect(screen.getByText('Created Date')).toBeTruthy();
                expect(screen.getByText('Submitted Date')).toBeTruthy();
                expect(screen.getByText('Modified Date')).toBeTruthy();

                expect(
                    screen.getAllByText('12/11/2020').length
                ).toBeGreaterThan(0);
                expect(
                    screen.getAllByText('12/11/2021').length
                ).toBeGreaterThan(0);
                expect(
                    screen.getAllByText('12/12/2021').length
                ).toBeGreaterThan(0);
            }
        );

        test('renders each date in its own column', async () => {
            const { container } = renderComponent(
                defaultRouteParams.tab,
                defaultRouteParams.page,
                { identity: { isEmployee: true, ...adminUxFlagOn } }
            );
            await act(async () => {
                await waitForLoaded();
            });

            // title | song id | songwriters | recordings | ownership | created |
            // submitted | modified | delivered | actions
            const cells = container.querySelectorAll(
                'table tbody tr:first-child td'
            );
            expect(cells[5].textContent).toEqual('12/11/2020');
            expect(cells[6].textContent).toEqual('12/11/2021');
            expect(cells[7].textContent).toEqual('12/12/2021');
        });

        test('leaves the submitted cell empty for a song that was never submitted', async () => {
            jest.spyOn(compositions, 'useCompositions').mockReturnValue({
                error: undefined,
                loading: false,
                data: {
                    totalCount: 1,
                    compositions: [
                        {
                            ...compositionData.compositions[0],
                            draft: true,
                            submittedAt: null,
                        },
                    ],
                },
            });

            const { container } = renderComponent(
                'drafts',
                defaultRouteParams.page,
                {
                    identity: { isEmployee: true, ...adminUxFlagOn },
                }
            );
            await act(async () => {
                await waitForLoaded();
            });

            const cells = container.querySelectorAll(
                'table tbody tr:first-child td'
            );
            expect(cells[6].textContent).toEqual('');
            expect(screen.queryByText('Invalid date')).toBeFalsy();

            // the other two dates are still rendered
            expect(cells[5].textContent).toEqual('12/11/2020');
            expect(cells[7].textContent).toEqual('12/12/2021');
        });
    });

    describe('Date column with the flag off', () => {
        // title | song id | songwriters | recordings | ownership | date |
        // actions
        const DATE_CELL = 5;

        test('renders a single submitted date column on the submitted tab', async () => {
            const { container } = renderComponent('submitted');
            await act(async () => {
                await waitForLoaded();
            });

            const headers = container.querySelectorAll('table thead th');
            expect(headers).toHaveLength(7);
            expect(headers[DATE_CELL].textContent).toEqual('Submitted Date');

            const cells = container.querySelectorAll(
                'table tbody tr:first-child td'
            );
            expect(cells).toHaveLength(7);
            expect(cells[DATE_CELL].textContent).toEqual('12/11/2021');
        });

        test('renders a single modified date column on the drafts tab', async () => {
            const { container } = renderComponent('drafts');
            await act(async () => {
                await waitForLoaded();
            });

            const headers = container.querySelectorAll('table thead th');
            expect(headers).toHaveLength(7);
            expect(headers[DATE_CELL].textContent).toEqual('Modified Date');

            const cells = container.querySelectorAll(
                'table tbody tr:first-child td'
            );
            expect(cells[DATE_CELL].textContent).toEqual('12/12/2021');
        });

        test('falls back to the created date on the drafts tab', async () => {
            jest.spyOn(compositions, 'useCompositions').mockReturnValue({
                error: undefined,
                loading: false,
                data: {
                    totalCount: 1,
                    compositions: [
                        {
                            ...compositionData.compositions[0],
                            draft: true,
                            modifiedAt: null,
                        },
                    ],
                },
            });

            const { container } = renderComponent('drafts');
            await act(async () => {
                await waitForLoaded();
            });

            const cells = container.querySelectorAll(
                'table tbody tr:first-child td'
            );
            expect(cells[DATE_CELL].textContent).toEqual('12/11/2020');
        });

        test('does not render the created or delivered columns', async () => {
            renderComponent();
            await act(async () => {
                await waitForLoaded();
            });

            expect(screen.queryByText('Created Date')).toBeFalsy();
            expect(screen.queryByText('Delivered')).toBeFalsy();
            expect(screen.queryByText('Modified Date')).toBeFalsy();
        });
    });

    test('has zero states in rows', async () => {
        renderComponent();
        await act(async () => {
            await waitForLoaded();
        });
        expect(screen.getByText('None added')).toBeTruthy();
        expect(screen.getByText('None')).toBeTruthy();
        expect(screen.getByText('No ownership')).toBeTruthy();
    });

    test('can edit through the options glyph', async () => {
        const { container, getByText } = renderComponentWithHistory();

        const glyphIcon = container.querySelector(
            '.DropdownIconButton.actions .OptionsHorizontalGlyphIcon'
        )?.parentElement;
        if (!glyphIcon)
            throw new Error('Could not find .OptionsHorizontalGlyphIcon');
        fireEvent.click(glyphIcon);

        expect(Segment.trackEvent).toHaveBeenCalledWith('Click - ...', {
            category: 'Songs',
        });

        const editButton = getByText('Edit');
        fireEvent.click(editButton);

        await waitFor(() =>
            expect(Segment.trackEvent).toHaveBeenCalledWith(
                'Click - Edit Song',
                { category: 'Songs' }
            )
        );

        let pathnameFound = false;
        await waitFor(() =>
            memoryHistory.entries.forEach(entry => {
                if (entry.pathname === '/song/1') pathnameFound = true;
            })
        );

        expect(pathnameFound).toBe(true);
    });

    test('can create a new song', async () => {
        jest.spyOn(applicationContext, 'useApplicationContext').mockReturnValue(
            {
                label: labelMock.data[0],
                setLabel: jest.fn(),
                labelAlt: null,
                setLabelAlt: jest.fn(),
                pageFilters: { songPageSongWriters: null, brand: null },
                updatePageFilters: jest.fn(),
            }
        );

        renderComponentWithHistory();
        await act(async () => {
            await waitForLoaded();
        });

        await userEvent.click(screen.getByText('New Song'));

        expect(Segment.trackEvent).toHaveBeenCalledWith('Click - New Song', {
            category: 'Songs',
        });

        let pathnameFound = false;
        await waitFor(() =>
            memoryHistory.entries.forEach(entry => {
                if (entry.pathname === '/song/new') pathnameFound = true;
            })
        );

        expect(pathnameFound).toBe(true);
    });

    test('can not delete a song if the feature flag is not enabled', async () => {
        const { container } = renderComponent();
        await act(async () => {
            await waitForLoaded();
        });
        const glyphIcon = container.querySelector(
            '.DropdownIconButton.actions .OptionsHorizontalGlyphIcon'
        )?.parentElement;
        if (!glyphIcon)
            throw new Error('Could not find .OptionsHorizontalGlyphIcon');
        fireEvent.click(glyphIcon);

        await waitFor(() =>
            expect(
                container.querySelectorAll(
                    '.SuiteListView-list .SuiteListView-option'
                )
            ).toHaveLength(1)
        );
    });

    test('can delete an existing song when FF is on', () => {
        const { container, getByText } = renderComponent(
            defaultRouteParams.tab,
            defaultRouteParams.page,
            {
                identity: {
                    features: { [FEATURE_FLAGS.ALLOW_DELETE_SONG]: true },
                },
            }
        );

        const glyphIcon = container.querySelector(
            '.DropdownIconButton.actions .OptionsHorizontalGlyphIcon'
        )?.parentElement;
        if (!glyphIcon)
            throw new Error('Could not find .OptionsHorizontalGlyphIcon');
        fireEvent.click(glyphIcon);

        const deleteButton = getByText('Delete');
        fireEvent.click(deleteButton);

        const confirmDeleteButton = getByText(
            'Yes, I want to delete this song'
        );
        expect(confirmDeleteButton).toBeTruthy();

        fireEvent.click(confirmDeleteButton);
        expect(Segment.trackEvent).toHaveBeenCalledWith('Click - Delete Song', {
            category: 'Delete Song',
            songId: '1',
            songTitle: 'Be Honest',
        });
        expect(deleteMutationSpy).toHaveBeenCalledWith({
            variables: { id: '1' },
        });
    });

    test('can switch tabs', async () => {
        renderComponentWithHistory(defaultRouteParams.tab);

        const draftTab = screen.getByText('Drafts');
        fireEvent.click(draftTab);

        expect(Segment.trackEvent).toHaveBeenCalledWith('Click - Drafts Tab', {
            category: 'Songs',
        });

        let pathnameAndSearchFound = false;
        await waitFor(() =>
            memoryHistory.entries.forEach(entry => {
                if (
                    entry.pathname === '/songs/drafts' &&
                    entry.search === '?page=1'
                )
                    pathnameAndSearchFound = true;
            })
        );

        expect(pathnameAndSearchFound).toBe(true);
    });

    test('saves search text when switching tabs', async () => {
        renderComponent();

        const text = '50 C';
        const searchInput = screen.getByPlaceholderText('Search...');
        fireEvent.change(searchInput, { target: { value: text } });

        const draftTab = screen.getByText('Drafts');
        fireEvent.click(draftTab);
        const searchInputDraft = screen.getByPlaceholderText(
            'Search...'
        ) as HTMLInputElement;
        expect(searchInputDraft.value).toBe(text);
    });

    test('can search songs', async () => {
        renderComponent();
        await act(async () => {
            await waitForLoaded();
        });

        const searchInput = screen.getByPlaceholderText(
            'Search...'
        ) as HTMLInputElement;
        fireEvent.change(searchInput, { target: { value: '50 C' } });
        expect(searchInput.value).toBe('50 C');
    });

    test('debounces the search input before querying', async () => {
        renderComponent();
        await act(async () => {
            await waitForLoaded();
        });

        compositionsFn.mockClear();

        const searchInput = screen.getByPlaceholderText(
            'Search...'
        ) as HTMLInputElement;
        fireEvent.change(searchInput, { target: { value: '5' } });
        fireEvent.change(searchInput, { target: { value: '50' } });
        fireEvent.change(searchInput, { target: { value: '50 C' } });

        // the input itself stays responsive
        expect(searchInput.value).toBe('50 C');

        // but nothing has been queried with the typed term yet
        expect(compositionsFn).not.toHaveBeenCalledWith(
            expect.objectContaining({ titleSearch: expect.anything() }),
            defaultRowsPerPage
        );

        await waitFor(() =>
            expect(compositionsFn).toHaveBeenLastCalledWith(
                expect.objectContaining({ titleSearch: '50 C' }),
                defaultRowsPerPage
            )
        );

        // only the final term was ever queried, not the intermediate keystrokes
        const queriedTerms = compositionsFn.mock.calls
            .map(([compositionsFilter]) => compositionsFilter.titleSearch)
            .filter(Boolean);
        expect([...new Set(queriedTerms)]).toEqual(['50 C']);
    });

    test('queries with the pre-flag filter keys when the flag is off', async () => {
        renderComponent();
        await act(async () => {
            await waitForLoaded();
        });

        // The gated keys must be absent, not merely undefined, so that the
        // query is byte for byte the one that shipped before the flag.
        const queriedFilter = lastCompositionsFilter();
        expect(Object.keys(queriedFilter).sort()).toEqual([
            'brand',
            'draft',
            'label',
            'page',
            'songWriters',
            'titleSearch',
        ]);
    });

    describe('Delivered', () => {
        const openDeliveredSelector = (container: HTMLElement) => {
            const selector = container.querySelector<HTMLElement>(
                '.SuiteSelect.DeliveredSelector'
            );
            if (!selector) throw new Error('Could not find .DeliveredSelector');

            const input = selector.querySelector(
                '.SuiteSelect-input-value-label'
            );
            if (!input) throw new Error('Could not find the selector input');
            fireEvent.click(input);

            return selector;
        };

        test('shows a delivered column', async () => {
            const { container } = renderComponent(
                defaultRouteParams.tab,
                defaultRouteParams.page,
                { identity: { isEmployee: true, ...adminUxFlagOn } }
            );
            await act(async () => {
                await waitForLoaded();
            });

            const headers = container.querySelectorAll('table thead th');
            expect(headers[8].textContent).toEqual('Delivered');

            // first mock composition is delivered, the second is not
            const rows = container.querySelectorAll('table tbody tr');
            expect(rows[0].querySelectorAll('td')[8].textContent).toEqual(
                'Yes'
            );
            expect(rows[1].querySelectorAll('td')[8].textContent).toEqual('No');
        });

        test('defaults to no delivered filter', async () => {
            renderComponent(defaultRouteParams.tab, defaultRouteParams.page, {
                identity: adminUxFlagOn,
            });
            await act(async () => {
                await waitForLoaded();
            });

            expect(compositionsFn).toHaveBeenLastCalledWith(
                expect.objectContaining({ delivered: undefined }),
                defaultRowsPerPage
            );
        });

        test('can filter to delivered songs only', async () => {
            const { container } = renderComponent(
                defaultRouteParams.tab,
                defaultRouteParams.page,
                { identity: adminUxFlagOn }
            );
            await act(async () => {
                await waitForLoaded();
            });

            const selector = openDeliveredSelector(container);
            fireEvent.click(within(selector).getByText('Delivered'));

            await waitFor(() =>
                expect(compositionsFn).toHaveBeenLastCalledWith(
                    expect.objectContaining({ delivered: true }),
                    defaultRowsPerPage
                )
            );
        });

        test('can filter to undelivered songs only', async () => {
            const { container } = renderComponent(
                defaultRouteParams.tab,
                defaultRouteParams.page,
                { identity: adminUxFlagOn }
            );
            await act(async () => {
                await waitForLoaded();
            });

            const selector = openDeliveredSelector(container);
            fireEvent.click(within(selector).getByText('Not Delivered'));

            await waitFor(() =>
                expect(compositionsFn).toHaveBeenLastCalledWith(
                    expect.objectContaining({ delivered: false }),
                    defaultRowsPerPage
                )
            );
        });

        test('is persisted in the url', async () => {
            const { container } = renderComponentWithHistory(
                defaultRouteParams.tab,
                defaultRouteParams.page,
                { identity: adminUxFlagOn }
            );
            await act(async () => {
                await waitForLoaded();
            });

            const selector = openDeliveredSelector(container);
            fireEvent.click(within(selector).getByText('Not Delivered'));

            await waitFor(() =>
                expect(
                    memoryHistory.entries.some(entry =>
                        entry.search.includes('delivered=false')
                    )
                ).toBe(true)
            );
        });

        test('is restored from the url', async () => {
            renderComponent(defaultRouteParams.tab, '1&delivered=true', {
                identity: adminUxFlagOn,
            });
            await act(async () => {
                await waitForLoaded();
            });

            expect(compositionsFn).toHaveBeenLastCalledWith(
                expect.objectContaining({ delivered: true }),
                defaultRowsPerPage
            );
        });

        describe('with the flag off', () => {
            test('does not render the delivery status filter', async () => {
                const { container } = renderComponent();
                await act(async () => {
                    await waitForLoaded();
                });

                expect(
                    container.querySelector('.SuiteSelect.DeliveredSelector')
                ).toBeFalsy();
                expect(screen.queryByText('Delivery Status')).toBeFalsy();
            });

            test('does not send a delivered filter, even with a delivered url param', async () => {
                renderComponent(defaultRouteParams.tab, '1&delivered=true');
                await act(async () => {
                    await waitForLoaded();
                });

                expect(lastCompositionsFilter()).not.toHaveProperty(
                    'delivered'
                );
            });
        });
    });

    describe('Song ID search', () => {
        test('searches by title and pub song ID for employees', async () => {
            renderComponent(defaultRouteParams.tab, defaultRouteParams.page, {
                identity: { isEmployee: true, ...adminUxFlagOn },
            });
            await act(async () => {
                await waitForLoaded();
            });

            await waitFor(() =>
                expect(compositionsFn).toHaveBeenLastCalledWith(
                    expect.objectContaining({ titleAndSongIdSearch: true }),
                    defaultRowsPerPage
                )
            );
        });

        test('does not search by pub song ID for non-employees', async () => {
            renderComponent(defaultRouteParams.tab, defaultRouteParams.page, {
                identity: { isEmployee: false, ...adminUxFlagOn },
            });
            await act(async () => {
                await waitForLoaded();
            });

            await waitFor(() =>
                expect(compositionsFn).toHaveBeenLastCalledWith(
                    expect.objectContaining({ titleAndSongIdSearch: false }),
                    defaultRowsPerPage
                )
            );
        });

        test('does not search by pub song ID for employees with the flag off', async () => {
            renderComponent(defaultRouteParams.tab, defaultRouteParams.page, {
                identity: { isEmployee: true },
            });
            await act(async () => {
                await waitForLoaded();
            });

            expect(lastCompositionsFilter()).not.toHaveProperty(
                'titleAndSongIdSearch'
            );
        });

        test('a non-numeric term is still passed through as a title search', async () => {
            renderComponent(defaultRouteParams.tab, defaultRouteParams.page, {
                identity: { isEmployee: true, ...adminUxFlagOn },
            });
            await act(async () => {
                await waitForLoaded();
            });

            const searchInput = screen.getByPlaceholderText('Search...');
            fireEvent.change(searchInput, { target: { value: 'Be Honest' } });

            // The term is sent untouched: the backend matches it against the
            // title, and `toFloat` of a non-numeric term is null so the pub
            // song ID comparison is simply false.
            await waitFor(() =>
                expect(compositionsFn).toHaveBeenLastCalledWith(
                    expect.objectContaining({
                        titleSearch: 'Be Honest',
                        titleAndSongIdSearch: true,
                    }),
                    defaultRowsPerPage
                )
            );
        });

        test('a numeric term is passed through unchanged for the ID match', async () => {
            renderComponent(defaultRouteParams.tab, defaultRouteParams.page, {
                identity: { isEmployee: true, ...adminUxFlagOn },
            });
            await act(async () => {
                await waitForLoaded();
            });

            const searchInput = screen.getByPlaceholderText('Search...');
            fireEvent.change(searchInput, { target: { value: '123' } });

            await waitFor(() =>
                expect(compositionsFn).toHaveBeenLastCalledWith(
                    expect.objectContaining({
                        titleSearch: '123',
                        titleAndSongIdSearch: true,
                    }),
                    defaultRowsPerPage
                )
            );
        });
    });

    test('can filter by songwriters', async () => {
        renderComponent();
        await act(async () => {
            await waitForLoaded();
        });

        const searchSelect = screen
            .getByTestId('SongWriterMultiSelector')
            .querySelector('button');
        if (searchSelect) fireEvent.click(searchSelect);

        const sw = await screen.findByText('50 Cent');
        await waitFor(async () => {
            expect(sw).toBeInTheDocument();
        });
        expect(songWritersFn).toHaveBeenCalled();
    });

    test('can filter by label', async () => {
        renderComponent();

        await waitForLoaded();

        const asyncSelect = screen.getByTestId(
            'SearchDropdown_labelSearchDropdown'
        );
        await userEvent.type(asyncSelect, 'Cow');

        expect(asyncSelect).toBeInTheDocument();
        expect(songWritersFn).toHaveBeenCalled();
    });

    test('can count Associated Recordings', async () => {
        const { container } = renderComponent();
        await act(async () => {
            await waitForLoaded();
        });
        const count = container.querySelector(
            '[data-testid=PageBody] tbody tr td:nth-child(4)'
        );
        expect(count?.textContent).toEqual('2');
    });

    test('can show empty state', async () => {
        const data = {
            totalCount: 0,
            compositions: [],
        };

        jest.spyOn(compositions, 'useCompositions').mockReturnValue({
            data,
            loading: false,
            error: undefined,
        });
        renderComponent();
        await act(async () => {
            await waitForLoaded();
        });
        expect(screen.getByText('You Have No Songs')).toBeTruthy();
    });

    test('shows loading state', async () => {
        jest.spyOn(compositions, 'useCompositions').mockReturnValue({
            data: compositionDataLoading,
            loading: true,
            error: undefined,
        });

        renderComponent();
        await act(async () => {
            await waitForLoaded();
        });
        expect(screen.getByText('Searching Songs')).toBeTruthy();
    });

    test('Label Picker should be rendered at header', async () => {
        const { container } = renderComponent(
            defaultRouteParams.tab,
            defaultRouteParams.page
        );
        await act(async () => {
            await waitForLoaded();
        });

        const headerLabelPicker = container.querySelector(
            '.SongListPage .PageHeader .HeaderLabelPicker .LabelSearchDropdown'
        );
        expect(headerLabelPicker).toBeTruthy();

        const pageTitle = container.querySelector(
            '.SongListPage .PageHeader .PageTitle'
        );
        expect(pageTitle).toBeFalsy();

        const listingLabelPickers = container.querySelector(
            '.SongListPage .PageBody .PageToolbar .LabelSearchDropdown'
        );
        expect(listingLabelPickers).toBeFalsy();
    });

    test('user with single label can save filter state', async () => {
        jest.spyOn(applicationContext, 'useApplicationContext').mockReturnValue(
            {
                label: labelMock.data[0],
                setLabel: jest.fn(),
                labelAlt: null,
                setLabelAlt: jest.fn(),
                pageFilters: { songPageSongWriters: null, brand: null },
                updatePageFilters: jest.fn(),
            }
        );

        renderComponent(defaultRouteParams.tab, defaultRouteParams.page);
        await act(async () => {
            await waitForLoaded();
        });

        const searchInput = screen.getByPlaceholderText('Search...');
        fireEvent.change(searchInput, { target: { value: '50 C' } });

        // song writer select
        const searchSelect = screen
            .getByTestId('SongWriterMultiSelector')
            .querySelector('button');
        if (searchSelect) fireEvent.click(searchSelect);

        const sw = await screen.findByText('50 Cent');
        fireEvent.click(sw);

        renderComponent(defaultRouteParams.tab, defaultRouteParams.page);
        await waitForLoaded();

        await waitFor(() =>
            expect(compositionsFn).toHaveBeenCalledWith(
                {
                    brand: null,
                    draft: false,
                    label: labelMock.data[0],
                    page: 1,
                    songWriters: ['50 Cent'],
                    titleSearch: '50 C',
                },
                defaultRowsPerPage
            )
        );
    });

    test('takes selected songwriters from the context', async () => {
        jest.spyOn(applicationContext, 'useApplicationContext').mockReturnValue(
            {
                label: labelMock.data[0],
                setLabel: jest.fn(),
                labelAlt: null,
                setLabelAlt: jest.fn(),
                pageFilters: {
                    songPageSongWriters: 'SW #1,SW #2',
                    brand: CompanyBrandName.THEORCHARD,
                },
                updatePageFilters: jest.fn(),
            }
        );

        const { container } = renderComponent(
            defaultRouteParams.tab,
            defaultRouteParams.page,
            {
                identity: {
                    isEmployee: true,
                    resources: [{ id: '*', name: 'All Orchard Labels' }],
                },
            }
        );
        await act(async () => {
            await waitForLoaded();
        });

        const selectedWriters = screen.getAllByTestId(
            'SuiteSelectInputValue'
        ) as HTMLInputElement[];
        expect(selectedWriters[0].value).toEqual('SW #1,SW #2');

        const brandSelectorContainer = container.querySelector(
            '.SuiteSelect.BrandSelector .SuiteSelect-input-value-label'
        );
        expect(brandSelectorContainer?.textContent).toEqual('The Orchard');
    });

    test('user with several labels can save filter state', async () => {
        jest.spyOn(applicationContext, 'useApplicationContext')
            .mockReturnValueOnce({
                label: {
                    name: 'Show all labels',
                    id: { subaccountId: 0, vendorId: 0 },
                    uuid: 'all',
                },
                setLabel: jest.fn(),
                labelAlt: null,
                setLabelAlt: jest.fn(),
                pageFilters: { songPageSongWriters: null, brand: null },
                updatePageFilters: jest.fn(),
            })
            .mockReturnValue({
                label: labelsMock.data[0],
                setLabel: jest.fn(),
                labelAlt: null,
                setLabelAlt: jest.fn(),
                pageFilters: { songPageSongWriters: null, brand: null },
                updatePageFilters: jest.fn(),
            });

        const { container } = renderComponent();
        await act(async () => {
            await waitForLoaded();
        });
        const searchInput = screen.getByPlaceholderText('Search...');
        fireEvent.change(searchInput, { target: { value: '50 C' } });

        // label select
        const labelSelect = container.querySelectorAll(
            '.LabelSearchDropdown input'
        )[0];
        fireEvent.mouseDown(labelSelect);
        const labelOptionSelect = container.querySelectorAll(
            '.LabelSearchDropdown .Select__option'
        )[1];
        fireEvent.mouseDown(labelOptionSelect);

        // song writer select
        const searchSelect = screen
            .getByTestId('SongWriterMultiSelector')
            .querySelector('button');
        if (searchSelect) fireEvent.click(searchSelect);

        const sw = await screen.findByText('50 Cent');
        fireEvent.click(sw);

        await waitFor(() =>
            expect(compositionsFn).toHaveBeenLastCalledWith(
                {
                    brand: null,
                    draft: false,
                    label: labelsMock.data[0],
                    page: 1,
                    songWriters: ['50 Cent'],
                    titleSearch: '50 C',
                },
                defaultRowsPerPage
            )
        );
    });

    test('New Button should be disabled, if the label is not chosen', async () => {
        const { container } = renderComponent(
            defaultRouteParams.tab,
            defaultRouteParams.page
        );
        await act(async () => {
            await waitForLoaded();
        });

        const newButtonDisabled = container.querySelector(
            '#NewSongButton .HelpTooltip button[disabled]'
        );
        expect(newButtonDisabled).toBeTruthy();
    });

    test('New Button should be enabled, if the label is chosen', async () => {
        jest.spyOn(applicationContext, 'useApplicationContext').mockReturnValue(
            {
                label: labelMock.data[0],
                setLabel: jest.fn(),
                labelAlt: null,
                setLabelAlt: jest.fn(),
                pageFilters: { songPageSongWriters: null, brand: null },
                updatePageFilters: jest.fn(),
            }
        );

        const { container } = renderComponent(
            defaultRouteParams.tab,
            defaultRouteParams.page
        );
        await act(async () => {
            await waitForLoaded();
        });

        const newButtonDisabled = container.querySelector(
            '#NewSongButton .HelpTooltip button[disabled]'
        );
        expect(newButtonDisabled).toBeFalsy();
        const newButton = container.querySelector('#NewSongButton button');
        expect(newButton).toBeTruthy();
    });

    test('column Song ID is shown for employees', async () => {
        const { container } = renderComponent(
            defaultRouteParams.tab,
            defaultRouteParams.page,
            { identity: { isEmployee: true } }
        );
        await act(async () => {
            await waitForLoaded();
        });

        const headerLabel = container.querySelector(
            'table thead tr th:nth-child(2)'
        );
        expect(headerLabel?.textContent).toEqual('Song ID');

        const firstRowValue = container.querySelector(
            'table tbody tr td:nth-child(2)'
        );
        expect(firstRowValue?.textContent).toEqual(
            compositionData.compositions[0].pubSongId.toString()
        );
    });

    test('column Song ID is not shown for non-employees', async () => {
        const { container } = renderComponent(
            defaultRouteParams.tab,
            defaultRouteParams.page,
            { identity: { isEmployee: false } }
        );
        await act(async () => {
            await waitForLoaded();
        });

        const headerLabel = container.querySelector(
            'table thead tr th:nth-child(2)'
        );
        expect(headerLabel?.textContent).toEqual('Songwriters');

        const firstRowValue = container.querySelector(
            'table tbody tr td:nth-child(2)'
        );
        expect(firstRowValue?.textContent).not.toEqual(
            compositionData.compositions[0].pubSongId.toString()
        );
    });

    test('can change pagesize', async () => {
        const mockData = generateNMockCompositions();
        jest.spyOn(applicationContext, 'useApplicationContext').mockReturnValue(
            {
                label: null,
                setLabel: jest.fn(),
                labelAlt: null,
                setLabelAlt: jest.fn(),
                pageFilters: { songPageSongWriters: null, brand: null },
                updatePageFilters: jest.fn(),
            }
        );

        compositionsFn = jest
            .spyOn(compositions, 'useCompositions')
            .mockReturnValue({
                error: undefined,
                loading: false,
                data: mockData,
            });

        let selectedPageSize = 0;
        renderComponent(defaultRouteParams.tab, defaultRouteParams.page);
        await act(async () => {
            await waitForLoaded();
        });

        const paginationMessage = (
            await screen.findAllByTestId('SuitePagination-message')
        )[0];
        await waitFor(() => {
            expect(paginationMessage).toBeTruthy();
        });
        act(() => {
            paginationMessage.click();
        });

        selectedPageSize = parseInt(
            (await screen.findAllByTestId('SegmentedButton-btn-text'))[0]
                .textContent || '0',
            10
        );

        (await screen.findAllByTestId('SegmentedButton-btn-text'))[0].click();

        await waitFor(() => {
            expect(compositionsFn).toHaveBeenLastCalledWith(
                {
                    brand: null,
                    draft: false,
                    label: null,
                    page: 1,
                    songWriters: undefined,
                    titleSearch: undefined,
                },
                selectedPageSize
            );
        });
    });

    describe('Brand selector', () => {
        test('Is shown for employees with full access', async () => {
            const { getByText } = renderComponent(
                defaultRouteParams.tab,
                defaultRouteParams.page,
                {
                    identity: {
                        isEmployee: true,
                        resources: [{ id: '*', name: 'All Orchard Labels' }],
                    },
                }
            );
            await act(async () => {
                await waitForLoaded();
            });
            expect(getByText('Brand')).toBeInTheDocument();
        });

        test('Is not shown for employees with not full access', async () => {
            const { queryByText } = renderComponent(
                defaultRouteParams.tab,
                defaultRouteParams.page,
                {
                    identity: {
                        isEmployee: true,
                    },
                }
            );
            await act(async () => {
                await waitForLoaded();
            });
            expect(queryByText('Brand')).not.toBeInTheDocument();
        });

        test('Is not shown for non employees with full access', async () => {
            const { queryByText } = renderComponent(
                defaultRouteParams.tab,
                defaultRouteParams.page,
                {
                    identity: {
                        isEmployee: false,
                        resources: [{ id: '*', name: 'All Orchard Labels' }],
                    },
                }
            );
            await act(async () => {
                await waitForLoaded();
            });
            expect(queryByText('Brand')).not.toBeInTheDocument();
        });

        test('Is not shown for non employees with not full access', async () => {
            const { queryByText } = renderComponent(
                defaultRouteParams.tab,
                defaultRouteParams.page,
                {
                    identity: {
                        isEmployee: false,
                    },
                }
            );
            await act(async () => {
                await waitForLoaded();
            });
            expect(queryByText('Brand')).not.toBeInTheDocument();
        });

        test('Can filter by Orchard brand', async () => {
            const { getByText } = renderComponent(
                defaultRouteParams.tab,
                defaultRouteParams.page,
                {
                    identity: {
                        isEmployee: true,
                        resources: [{ id: '*', name: 'All Orchard Labels' }],
                    },
                }
            );
            await act(async () => {
                await waitForLoaded();
            });

            fireEvent.click(getByText('Brand'));
            fireEvent.click(getByText('The Orchard'));

            expect(compositionsFn).toHaveBeenLastCalledWith(
                {
                    brand: 'THEORCHARD',
                    draft: false,
                    label: null,
                    page: 1,
                    titleSearch: undefined,
                },
                defaultRowsPerPage
            );
        });

        test('Can filter by AWAL brand', async () => {
            const { getByText } = renderComponent(
                defaultRouteParams.tab,
                defaultRouteParams.page,
                {
                    identity: {
                        isEmployee: true,
                        resources: [{ id: '*', name: 'All Orchard Labels' }],
                    },
                }
            );
            await act(async () => {
                await waitForLoaded();
            });

            fireEvent.click(getByText('Brand'));
            fireEvent.click(getByText('AWAL'));

            expect(compositionsFn).toHaveBeenLastCalledWith(
                {
                    brand: 'AWAL',
                    draft: false,
                    label: null,
                    page: 1,
                    titleSearch: undefined,
                },
                defaultRowsPerPage
            );
        });
    });
});
