mirror of
https://github.com/actualbudget/actual.git
synced 2026-05-05 22:52:20 -05:00
Compare commits
2 Commits
matiss/crd
...
claude/hid
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fe4a2f573 | ||
|
|
263358b5cf |
7
.github/workflows/vrt-update-generate.yml
vendored
7
.github/workflows/vrt-update-generate.yml
vendored
@@ -65,6 +65,10 @@ jobs:
|
||||
ref: ${{ steps.pr.outputs.head_sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Trust workspace directory
|
||||
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
shell: bash
|
||||
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
@@ -87,9 +91,6 @@ jobs:
|
||||
- name: Create patch with PNG changes only
|
||||
id: create-patch
|
||||
run: |
|
||||
# Trust the repository directory (required for container environments)
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
|
||||
192
packages/cli/src/commands/categories.test.ts
Normal file
192
packages/cli/src/commands/categories.test.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import * as api from '@actual-app/api';
|
||||
import { Command } from 'commander';
|
||||
|
||||
import { printOutput } from '#output';
|
||||
|
||||
import { registerCategoriesCommand } from './categories';
|
||||
import { registerCategoryGroupsCommand } from './category-groups';
|
||||
|
||||
vi.mock('@actual-app/api', () => ({
|
||||
getCategories: vi.fn().mockResolvedValue([]),
|
||||
createCategory: vi.fn().mockResolvedValue('new-id'),
|
||||
updateCategory: vi.fn().mockResolvedValue(undefined),
|
||||
deleteCategory: vi.fn().mockResolvedValue(undefined),
|
||||
getCategoryGroups: vi.fn().mockResolvedValue([]),
|
||||
createCategoryGroup: vi.fn().mockResolvedValue('new-group-id'),
|
||||
updateCategoryGroup: vi.fn().mockResolvedValue(undefined),
|
||||
deleteCategoryGroup: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
vi.mock('#connection', () => ({
|
||||
withConnection: vi.fn((_opts, fn) => fn()),
|
||||
}));
|
||||
|
||||
vi.mock('#output', () => ({
|
||||
printOutput: vi.fn(),
|
||||
}));
|
||||
|
||||
function createProgram(): Command {
|
||||
const program = new Command();
|
||||
program.option('--format <format>');
|
||||
program.option('--server-url <url>');
|
||||
program.option('--password <pw>');
|
||||
program.option('--session-token <token>');
|
||||
program.option('--sync-id <id>');
|
||||
program.option('--data-dir <dir>');
|
||||
program.option('--verbose');
|
||||
program.exitOverride();
|
||||
registerCategoriesCommand(program);
|
||||
registerCategoryGroupsCommand(program);
|
||||
return program;
|
||||
}
|
||||
|
||||
async function run(args: string[]) {
|
||||
const program = createProgram();
|
||||
await program.parseAsync(['node', 'test', ...args]);
|
||||
}
|
||||
|
||||
describe('categories list', () => {
|
||||
let stderrSpy: ReturnType<typeof vi.spyOn>;
|
||||
let stdoutSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
stderrSpy = vi
|
||||
.spyOn(process.stderr, 'write')
|
||||
.mockImplementation(() => true);
|
||||
stdoutSpy = vi
|
||||
.spyOn(process.stdout, 'write')
|
||||
.mockImplementation(() => true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stderrSpy.mockRestore();
|
||||
stdoutSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('filters out hidden categories by default', async () => {
|
||||
vi.mocked(api.getCategories).mockResolvedValue([
|
||||
{ id: '1', name: 'Visible', group_id: 'g1', hidden: false },
|
||||
{ id: '2', name: 'Hidden', group_id: 'g1', hidden: true },
|
||||
]);
|
||||
|
||||
await run(['categories', 'list']);
|
||||
|
||||
expect(printOutput).toHaveBeenCalledWith(
|
||||
[{ id: '1', name: 'Visible', group_id: 'g1', hidden: false }],
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it('includes hidden categories when --include-hidden is passed', async () => {
|
||||
vi.mocked(api.getCategories).mockResolvedValue([
|
||||
{ id: '1', name: 'Visible', group_id: 'g1', hidden: false },
|
||||
{ id: '2', name: 'Hidden', group_id: 'g1', hidden: true },
|
||||
]);
|
||||
|
||||
await run(['categories', 'list', '--include-hidden']);
|
||||
|
||||
expect(printOutput).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: '2', hidden: true }),
|
||||
]),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it('passes format option to printOutput', async () => {
|
||||
vi.mocked(api.getCategories).mockResolvedValue([]);
|
||||
|
||||
await run(['--format', 'csv', 'categories', 'list']);
|
||||
|
||||
expect(printOutput).toHaveBeenCalledWith([], 'csv');
|
||||
});
|
||||
});
|
||||
|
||||
describe('category-groups list', () => {
|
||||
let stderrSpy: ReturnType<typeof vi.spyOn>;
|
||||
let stdoutSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
stderrSpy = vi
|
||||
.spyOn(process.stderr, 'write')
|
||||
.mockImplementation(() => true);
|
||||
stdoutSpy = vi
|
||||
.spyOn(process.stdout, 'write')
|
||||
.mockImplementation(() => true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stderrSpy.mockRestore();
|
||||
stdoutSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('filters out hidden groups and hidden child categories by default', async () => {
|
||||
vi.mocked(api.getCategoryGroups).mockResolvedValue([
|
||||
{
|
||||
id: 'g1',
|
||||
name: 'Visible Group',
|
||||
is_income: false,
|
||||
hidden: false,
|
||||
categories: [
|
||||
{ id: 'c1', name: 'Visible Cat', group_id: 'g1', hidden: false },
|
||||
{ id: 'c2', name: 'Hidden Cat', group_id: 'g1', hidden: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'g2',
|
||||
name: 'Hidden Group',
|
||||
is_income: false,
|
||||
hidden: true,
|
||||
categories: [],
|
||||
},
|
||||
]);
|
||||
|
||||
await run(['category-groups', 'list']);
|
||||
|
||||
expect(printOutput).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
id: 'g1',
|
||||
name: 'Visible Group',
|
||||
is_income: false,
|
||||
hidden: false,
|
||||
categories: [
|
||||
{ id: 'c1', name: 'Visible Cat', group_id: 'g1', hidden: false },
|
||||
],
|
||||
},
|
||||
],
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it('includes hidden groups and categories when --include-hidden is passed', async () => {
|
||||
vi.mocked(api.getCategoryGroups).mockResolvedValue([
|
||||
{
|
||||
id: 'g1',
|
||||
name: 'Visible Group',
|
||||
is_income: false,
|
||||
hidden: false,
|
||||
categories: [
|
||||
{ id: 'c2', name: 'Hidden Cat', group_id: 'g1', hidden: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'g2',
|
||||
name: 'Hidden Group',
|
||||
is_income: false,
|
||||
hidden: true,
|
||||
categories: [],
|
||||
},
|
||||
]);
|
||||
|
||||
await run(['category-groups', 'list', '--include-hidden']);
|
||||
|
||||
const output = vi.mocked(printOutput).mock.calls[0][0] as Array<{
|
||||
id: string;
|
||||
}>;
|
||||
expect(output).toHaveLength(2);
|
||||
expect(output.map(g => g.id)).toEqual(['g1', 'g2']);
|
||||
});
|
||||
});
|
||||
@@ -12,13 +12,17 @@ export function registerCategoriesCommand(program: Command) {
|
||||
|
||||
categories
|
||||
.command('list')
|
||||
.description('List all categories')
|
||||
.action(async () => {
|
||||
.description('List categories (excludes hidden by default)')
|
||||
.option('--include-hidden', 'Include hidden categories', false)
|
||||
.action(async cmdOpts => {
|
||||
const opts = program.opts();
|
||||
await withConnection(
|
||||
opts,
|
||||
async () => {
|
||||
const result = await api.getCategories();
|
||||
const allCategories = await api.getCategories();
|
||||
const result = allCategories.filter(
|
||||
c => cmdOpts.includeHidden || !c.hidden,
|
||||
);
|
||||
printOutput(result, opts.format);
|
||||
},
|
||||
{ mutates: false },
|
||||
|
||||
@@ -12,13 +12,22 @@ export function registerCategoryGroupsCommand(program: Command) {
|
||||
|
||||
groups
|
||||
.command('list')
|
||||
.description('List all category groups')
|
||||
.action(async () => {
|
||||
.description('List category groups (excludes hidden by default)')
|
||||
.option('--include-hidden', 'Include hidden groups and categories', false)
|
||||
.action(async cmdOpts => {
|
||||
const opts = program.opts();
|
||||
await withConnection(
|
||||
opts,
|
||||
async () => {
|
||||
const result = await api.getCategoryGroups();
|
||||
const allGroups = await api.getCategoryGroups();
|
||||
const result = cmdOpts.includeHidden
|
||||
? allGroups
|
||||
: allGroups
|
||||
.filter(g => !g.hidden)
|
||||
.map(g => ({
|
||||
...g,
|
||||
categories: g.categories?.filter(c => !c.hidden),
|
||||
}));
|
||||
printOutput(result, opts.format);
|
||||
},
|
||||
{ mutates: false },
|
||||
|
||||
6
upcoming-release-notes/7699.md
Normal file
6
upcoming-release-notes/7699.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [matt-fidd]
|
||||
---
|
||||
|
||||
Fix VRT update workflow failing
|
||||
6
upcoming-release-notes/cli-list-hidden-categories.md
Normal file
6
upcoming-release-notes/cli-list-hidden-categories.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: []
|
||||
---
|
||||
|
||||
CLI: `categories list` and `category-groups list` now exclude hidden entries by default. Pass `--include-hidden` to include them.
|
||||
Reference in New Issue
Block a user