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: 1 addition & 3 deletions src/components/CategoryEditTree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,11 @@ export default {
counter++;
}

this.categoryStore.addClass({
const lastId = this.categoryStore.addClass({
name: parent.name.concat([name]),
rule: { type: 'regex', regex: 'FILL ME' },
});

// Find the category with the max ID, and open an editor for it
const lastId = _.max(_.map(this.categoryStore.classes, 'id'));
this.editingId = lastId;
},
showEditModal: function () {
Expand Down
7 changes: 4 additions & 3 deletions src/stores/categories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ export const useCategoryStore = defineStore('categories', {
const parent_depth = old_class.name.length;

if (new_class.id === undefined || new_class.id === null) {
new_class.id = _.max(_.map(this.classes, 'id')) + 1;
new_class.id = (_.max(_.map(this.classes, 'id')) ?? -1) + 1;
this.classes.push(new_class);
} else {
Object.assign(old_class, new_class);
Expand All @@ -345,10 +345,11 @@ export const useCategoryStore = defineStore('categories', {

this.classes_unsaved_changes = true;
},
addClass(this: State, new_class: Category) {
new_class.id = _.max(_.map(this.classes, 'id')) + 1;
addClass(this: State, new_class: Category): number {
new_class.id = (_.max(_.map(this.classes, 'id')) ?? -1) + 1;
this.classes.push(new_class);
this.classes_unsaved_changes = true;
return new_class.id;
},
removeClass(this: State, classId: number) {
this.classes = this.classes.filter((c: Category) => c.id !== classId);
Expand Down
5 changes: 1 addition & 4 deletions src/views/settings/CategorizationSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ import 'vue-awesome/icons/angle-double-up';

import { useCategoryStore } from '~/stores/categories';

import _ from 'lodash';
import { downloadFile } from '~/util/export';

export default {
Expand Down Expand Up @@ -149,12 +148,10 @@ export default {
},
methods: {
addClass: function () {
this.categoryStore.addClass({
const lastId = this.categoryStore.addClass({
name: ['New class'],
rule: { type: 'regex', regex: 'FILL ME' },
});

const lastId = _.max(_.map(this.categoryStore.classes, 'id'));
this.editingId = lastId;
},
saveClasses: async function () {
Expand Down
5 changes: 1 addition & 4 deletions src/views/settings/CategoryBuilder.vue
Original file line number Diff line number Diff line change
Expand Up @@ -291,13 +291,10 @@ export default {
},
createRule(word: string) {
console.log('Opening modal for creating rule with word: ' + word);
this.categoryStore.addClass({
const lastId = this.categoryStore.addClass({
name: [word],
rule: { type: 'regex', regex: _.escapeRegExp(word) },
});

// Find the category with the max ID, and open an editor for it
const lastId = _.max(_.map(this.categoryStore.classes, 'id'));
this.create.word = word;
this.create.categoryId = lastId;
},
Expand Down
25 changes: 25 additions & 0 deletions test/unit/store/categories.test.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,29 @@ describe('categories store', () => {
expect(decoded).toEqual(42);
expect(encoded).toEqual(42);
});

test('addClass after clearAll assigns id 0, not NaN/null', () => {
// Regression test for #427: _.max([]) returns undefined, so
// `_.max(_.map(this.classes, 'id')) + 1` evaluated to NaN for an empty
// class list. JSON.stringify(NaN) produces null, creating an uneditable category.
expect(categoryStore.classes).toHaveLength(0);
const id = categoryStore.addClass({ name: ['New class'], rule: { type: 'none' } });
expect(id).toBe(0);
expect(categoryStore.classes[0].id).toBe(0);
// Verify the new category is immediately editable (updateClass should find it by id)
categoryStore.updateClass({ ...categoryStore.classes[0], name: ['Renamed'] });
expect(categoryStore.classes[0].name).toEqual(['Renamed']);
});

test('addClass after partial deletion starts from max existing id', () => {
categoryStore.load([
{ name: ['A'], rule: { type: 'none' } },
{ name: ['B'], rule: { type: 'none' } },
{ name: ['C'], rule: { type: 'none' } },
]);
const maxBefore = Math.max(...categoryStore.classes.map(c => c.id ?? -1));
categoryStore.removeClass(categoryStore.classes[1].id);
const id = categoryStore.addClass({ name: ['D'], rule: { type: 'none' } });
expect(id).toBe(maxBefore + 1);
});
});
Loading