Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions playwright/fixtures/data/exportBlueprintContents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,14 +223,14 @@ timezone = "${tz}"
languages = [ "C.UTF-8" ]

[[packages]]
name = "vim-minimal"
name = "tmux"
version = "*"

[[packages]]
name = "bash"
version = "*"

[[packages]]
name = "tmux"
name = "vim-minimal"
version = "*"`;
};
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ const Packages = () => {
<PackagesTable
isSuccessEpelRepo={isSuccessEpelRepo}
epelRepo={epelRepo}
activeStream={activeStream}
/>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,9 @@ import RetirementDate from './RetirementDate';
type PackagesTableProps = {
isSuccessEpelRepo: boolean;
epelRepo: ApiRepositoryCollectionResponseRead | undefined;
activeStream: string;
};

const PackagesTable = ({
isSuccessEpelRepo,
epelRepo,
activeStream,
}: PackagesTableProps) => {
const PackagesTable = ({ isSuccessEpelRepo, epelRepo }: PackagesTableProps) => {
const dispatch = useAppDispatch();
const recommendedRepositories = useAppSelector(selectRecommendedRepositories);
const packages = useAppSelector(selectPackages);
Expand Down Expand Up @@ -94,71 +89,11 @@ const PackagesTable = ({
}
};

const sortedPackages = useMemo(() => {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same sorting logic remains in PackageSearch where it still makes sense. In the case of packages table, there is no reason to sort the rows by stream, end date, etc. and thus adding the packages to the selected packages table in seemingly random places.

if (packages.length < 1 || !Array.isArray(packages)) {
return [];
}

return [...packages].sort((a, b) => {
// Active stream packages first (if activeStream is set)
const aIsActive = activeStream && a.stream === activeStream ? 0 : 1;
const bIsActive = activeStream && b.stream === activeStream ? 0 : 1;
if (aIsActive !== bIsActive) return aIsActive - bIsActive;

// Then by name (asc)
if (a.name !== b.name) return a.name.localeCompare(b.name);

// Then by stream version (desc)
const aStream = a.stream || '';
const bStream = b.stream || '';
if (aStream !== bStream) {
if (!aStream) return 1;
if (!bStream) return -1;
const aParts = aStream
.split('.')
.map((part) => parseInt(part, 10) || 0);
const bParts = bStream
.split('.')
.map((part) => parseInt(part, 10) || 0);
const aVersion = aParts
.map((p) => p.toString().padStart(10, '0'))
.join('.');
const bVersion = bParts
.map((p) => p.toString().padStart(10, '0'))
.join('.');
return bVersion.localeCompare(aVersion); // descending
}

// Then by end date (asc, nulls last)
const aEndDate = a.end_date || '9999-12-31';
const bEndDate = b.end_date || '9999-12-31';
if (aEndDate !== bEndDate) return aEndDate.localeCompare(bEndDate);

// Then by repository (asc)
const aRepo = a.repository || '';
const bRepo = b.repository || '';
if (aRepo !== bRepo) return aRepo.localeCompare(bRepo);

// Finally by module name (asc)
const aModule = a.module_name || '';
const bModule = b.module_name || '';
return aModule.localeCompare(bModule);
});
}, [packages, activeStream]);

const sortedGroups = useMemo(() => {
if (groups.length < 1 || !Array.isArray(groups)) {
return [];
}

return [...groups].sort((a, b) => a.name.localeCompare(b.name));
}, [groups]);

const composePkgTable = () => {
let rows: ReactElement[] = [];

rows = rows.concat(
sortedGroups.map((grp, rowIndex) => (
groups.map((grp, rowIndex) => (
<Tbody
key={`${grp.name}-${grp.repository || 'default'}`}
isExpanded={isGroupExpanded(grp.name)}
Expand Down Expand Up @@ -221,8 +156,8 @@ const PackagesTable = ({

// Render required (oscap) packages first, then user-added packages
const orderedPackages = [
...sortedPackages.filter((pkg) => requiredSet.has(pkg.name)),
...sortedPackages.filter((pkg) => !requiredSet.has(pkg.name)),
...packages.filter((pkg) => requiredSet.has(pkg.name)),
...packages.filter((pkg) => !requiredSet.has(pkg.name)),
];

rows = rows.concat(
Expand Down Expand Up @@ -267,15 +202,7 @@ const PackagesTable = ({
return composePkgTable();
// Would need significant rewrite to fix this
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
packages.length,
groups.length,
recommendedRepositories,
expandedGroups,
sortedPackages,
sortedGroups,
requiredSet,
]);
}, [packages, groups, recommendedRepositories, expandedGroups, requiredSet]);

return (
<Table data-testid='packages-table' style={{ tableLayout: 'fixed' }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,30 +550,27 @@ describe('Packages Component', () => {
// Verify packages appear in UI
const rows = await screen.findAllByTestId('package-row');
expect(rows).toHaveLength(2);
expect(rows[0]).toHaveTextContent('another-pkg');
expect(rows[1]).toHaveTextContent('preloaded-pkg');
expect(rows[0]).toHaveTextContent('preloaded-pkg');
expect(rows[1]).toHaveTextContent('another-pkg');
});
});

describe('Selected Packages View', () => {
test('selected packages are sorted alphabetically', async () => {
test('selected packages appear newest first', async () => {
fetchMock.mockResponse(createFetchHandler({ rpms: mockSearchResults }));
renderPackagesStep();
const user = createUser();

// Search and select all packages (in reverse order to verify sorting)
await typeIntoSearchBox(user, 'test');

await screen.findByRole('option', { name: /test-lib/ });

// Select packages in reverse alphabetical order
await selectPkgOption(user, 'testPkg');
await clickOnSearchBox(user);
await selectPkgOption(user, 'test-lib');
await clickOnSearchBox(user);
await selectPkgOption(user, 'test');

// Verify all packages are shown and sorted
const rows = await screen.findAllByTestId('package-row');
expect(rows).toHaveLength(3);
expect(rows[0]).toHaveTextContent('test');
Expand Down Expand Up @@ -764,7 +761,7 @@ describe('Packages Component', () => {
);

expect(dataRows).toHaveLength(4);
// Required packages first (alphabetical)
// Required packages first
expect(dataRows[0]).toHaveTextContent('aide');
expect(dataRows[0]).toHaveAttribute(
'data-testid',
Expand All @@ -775,10 +772,10 @@ describe('Packages Component', () => {
'data-testid',
'required-package-row',
);
// User packages after (alphabetical)
expect(dataRows[2]).toHaveTextContent('curl');
// User packages after
expect(dataRows[2]).toHaveTextContent('zsh');
expect(dataRows[2]).toHaveAttribute('data-testid', 'package-row');
expect(dataRows[3]).toHaveTextContent('zsh');
expect(dataRows[3]).toHaveTextContent('curl');
expect(dataRows[3]).toHaveAttribute('data-testid', 'package-row');
});

Expand Down
4 changes: 2 additions & 2 deletions src/store/slices/wizard/content/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const contentSlice = createSlice({
if (existingPackageIndex !== -1) {
state.packages[existingPackageIndex] = action.payload;
} else {
state.packages.push(action.payload);
state.packages.unshift(action.payload);
}
},
removePackage: (
Expand Down Expand Up @@ -136,7 +136,7 @@ export const contentSlice = createSlice({
if (existingGrpIndex !== -1) {
state.groups[existingGrpIndex] = action.payload;
} else {
state.groups.push(action.payload);
state.groups.unshift(action.payload);
}
},
removePackageGroup: (
Expand Down
23 changes: 21 additions & 2 deletions src/store/slices/wizard/content/tests/content.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ describe('content reducers', () => {

expect(state.content.packages).toHaveLength(3);
expect(state.content.packages.map((p) => p.name)).toEqual([
'vim',
'git',
'curl',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): Add corresponding tests for groups reducer to cover newest-first behaviour

The expectations for packages now correctly reflect the newest-first (unshift) behaviour. The same ordering change was applied to groups, but there’s no test asserting this.

Please add reducer tests for addPackageGroup that start from a non-empty groups array, add one or more groups, and verify that new groups are inserted at the beginning. This will keep coverage aligned with the reducer behaviour and guard against regressions in groups ordering.

'git',
'vim',
]);
});

Expand Down Expand Up @@ -255,6 +255,25 @@ describe('content reducers', () => {
expect(result.content.groups[0].name).toBe('Development Tools');
});

it('should insert new groups at the beginning', () => {
let state = wizardReducer(
initialState,
addPackageGroup(createGroup('Development Tools')),
);
state = wizardReducer(state, addPackageGroup(createGroup('Server')));
state = wizardReducer(
state,
addPackageGroup(createGroup('Networking')),
);

expect(state.content.groups).toHaveLength(3);
expect(state.content.groups.map((g) => g.name)).toEqual([
'Networking',
'Server',
'Development Tools',
]);
});

it('should update existing group if same name', () => {
const stateWithGroup: WizardState = {
...initialState,
Expand Down
Loading