Compare commits
11 Commits
MatissJani
...
claude/hid
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
428dea9823 | ||
|
|
984702dd6c | ||
|
|
2fd88c2d71 | ||
|
|
fdf1196f8a | ||
|
|
89dfd7b6d8 | ||
|
|
e5997f97a8 | ||
|
|
07b9dcacad | ||
|
|
2a3f943660 | ||
|
|
b9ab3e7bc6 | ||
|
|
4f40defe9e | ||
|
|
7fe4a2f573 |
17
.github/workflows/vrt-update-generate.yml
vendored
@@ -82,16 +82,17 @@ jobs:
|
||||
with:
|
||||
download-translations: 'false'
|
||||
- name: Build browser bundle
|
||||
# REACT_APP_NETLIFY=true keeps the "Create test file" button in the
|
||||
# production bundle — every VRT test's beforeEach relies on it via
|
||||
# ConfigurationPage.createTestFile().
|
||||
# REACT_APP_NETLIFY=true flips isNonProductionEnvironment() on in the
|
||||
# bundle so the "Create test file" button (used by every e2e beforeEach
|
||||
# via ConfigurationPage.createTestFile()) is still rendered in a
|
||||
# production build. Without it, e2e tests would time out waiting for
|
||||
# a button that was tree-shaken out.
|
||||
# --skip-translations keeps VRT screenshots deterministic by rendering
|
||||
# source-code English instead of upstream Weblate en.json (which can
|
||||
# drift between snapshot capture and test runs).
|
||||
env:
|
||||
REACT_APP_NETLIFY: 'true'
|
||||
run: |
|
||||
yarn workspace plugins-service build
|
||||
yarn workspace @actual-app/crdt build
|
||||
yarn workspace @actual-app/core build:browser
|
||||
yarn workspace @actual-app/web build:browser
|
||||
run: yarn build:browser --skip-translations
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
|
||||
@@ -204,8 +204,8 @@ export function getAccountBalance(id: APIAccountEntity['id'], cutoff?: Date) {
|
||||
return send('api/account-balance', { id, cutoff });
|
||||
}
|
||||
|
||||
export function getCategoryGroups() {
|
||||
return send('api/category-groups-get');
|
||||
export function getCategoryGroups(options: { hidden?: boolean } = {}) {
|
||||
return send('api/category-groups-get', options);
|
||||
}
|
||||
|
||||
export function createCategoryGroup(group: Omit<APICategoryGroupEntity, 'id'>) {
|
||||
@@ -226,8 +226,8 @@ export function deleteCategoryGroup(
|
||||
return send('api/category-group-delete', { id, transferCategoryId });
|
||||
}
|
||||
|
||||
export function getCategories() {
|
||||
return send('api/categories-get', { grouped: false });
|
||||
export function getCategories(options: { hidden?: boolean } = {}) {
|
||||
return send('api/categories-get', { grouped: false, ...options });
|
||||
}
|
||||
|
||||
export function createCategory(category: Omit<APICategoryEntity, 'id'>) {
|
||||
|
||||
131
packages/cli/src/commands/categories.test.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
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 commands', () => {
|
||||
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();
|
||||
});
|
||||
|
||||
describe('categories list', () => {
|
||||
it('asks the API to exclude hidden categories by default', async () => {
|
||||
await run(['categories', 'list']);
|
||||
|
||||
expect(api.getCategories).toHaveBeenCalledWith({ hidden: false });
|
||||
});
|
||||
|
||||
it('asks the API for all categories when --include-hidden is passed', async () => {
|
||||
await run(['categories', 'list', '--include-hidden']);
|
||||
|
||||
expect(api.getCategories).toHaveBeenCalledWith({});
|
||||
});
|
||||
|
||||
it('prints whatever the API returns', async () => {
|
||||
const visible = {
|
||||
id: '1',
|
||||
name: 'Visible',
|
||||
group_id: 'g1',
|
||||
hidden: false,
|
||||
};
|
||||
vi.mocked(api.getCategories).mockResolvedValue([visible]);
|
||||
|
||||
await run(['categories', 'list']);
|
||||
|
||||
expect(printOutput).toHaveBeenCalledWith([visible], 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', () => {
|
||||
it('asks the API to exclude hidden groups by default', async () => {
|
||||
await run(['category-groups', 'list']);
|
||||
|
||||
expect(api.getCategoryGroups).toHaveBeenCalledWith({ hidden: false });
|
||||
});
|
||||
|
||||
it('asks the API for all groups when --include-hidden is passed', async () => {
|
||||
await run(['category-groups', 'list', '--include-hidden']);
|
||||
|
||||
expect(api.getCategoryGroups).toHaveBeenCalledWith({});
|
||||
});
|
||||
|
||||
it('prints whatever the API returns', async () => {
|
||||
const group = {
|
||||
id: 'g1',
|
||||
name: 'Group',
|
||||
is_income: false,
|
||||
hidden: false,
|
||||
categories: [{ id: 'c1', name: 'Cat', group_id: 'g1', hidden: false }],
|
||||
};
|
||||
vi.mocked(api.getCategoryGroups).mockResolvedValue([group]);
|
||||
|
||||
await run(['category-groups', 'list']);
|
||||
|
||||
expect(printOutput).toHaveBeenCalledWith([group], undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -12,13 +12,16 @@ 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 result = await api.getCategories(
|
||||
cmdOpts.includeHidden ? {} : { hidden: false },
|
||||
);
|
||||
printOutput(result, opts.format);
|
||||
},
|
||||
{ mutates: false },
|
||||
|
||||
@@ -12,13 +12,16 @@ 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 result = await api.getCategoryGroups(
|
||||
cmdOpts.includeHidden ? {} : { hidden: false },
|
||||
);
|
||||
printOutput(result, opts.format);
|
||||
},
|
||||
{ mutates: false },
|
||||
|
||||
@@ -12,14 +12,14 @@ import { View } from './View';
|
||||
const backgroundColor: {
|
||||
[key in ButtonVariant | `${ButtonVariant}Disabled`]?: string;
|
||||
} = {
|
||||
normal: theme.buttonPrimaryBackground,
|
||||
normalDisabled: theme.buttonPrimaryBackground,
|
||||
normal: theme.buttonNormalBackground,
|
||||
normalDisabled: theme.buttonNormalDisabledBackground,
|
||||
primary: theme.buttonPrimaryBackground,
|
||||
primaryDisabled: theme.buttonPrimaryBackground,
|
||||
bare: theme.buttonPrimaryBackground,
|
||||
bareDisabled: theme.buttonPrimaryBackground,
|
||||
menu: theme.buttonPrimaryBackground,
|
||||
menuSelected: theme.buttonPrimaryBackground,
|
||||
primaryDisabled: theme.buttonPrimaryDisabledBackground,
|
||||
bare: theme.buttonBareBackground,
|
||||
bareDisabled: theme.buttonBareDisabledBackground,
|
||||
menu: theme.buttonMenuBackground,
|
||||
menuSelected: theme.buttonMenuSelectedBackground,
|
||||
};
|
||||
|
||||
const backgroundColorHover: Record<
|
||||
@@ -54,14 +54,14 @@ const borderColor: {
|
||||
const textColor: {
|
||||
[key in ButtonVariant | `${ButtonVariant}Disabled`]?: CSSProperties['color'];
|
||||
} = {
|
||||
normal: theme.buttonPrimaryText,
|
||||
normalDisabled: theme.buttonPrimaryText,
|
||||
normal: theme.buttonNormalText,
|
||||
normalDisabled: theme.buttonNormalDisabledText,
|
||||
primary: theme.buttonPrimaryText,
|
||||
primaryDisabled: theme.buttonPrimaryText,
|
||||
bare: theme.buttonPrimaryText,
|
||||
bareDisabled: theme.buttonPrimaryText,
|
||||
menu: theme.buttonPrimaryText,
|
||||
menuSelected: theme.buttonPrimaryText,
|
||||
primaryDisabled: theme.buttonPrimaryDisabledText,
|
||||
bare: theme.buttonBareText,
|
||||
bareDisabled: theme.buttonBareDisabledText,
|
||||
menu: theme.buttonMenuText,
|
||||
menuSelected: theme.buttonMenuSelectedText,
|
||||
};
|
||||
|
||||
const textColorHover: {
|
||||
|
||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 35 KiB |