Skip to content

Commit

Permalink
Feature/command dictionary (#1571)
Browse files Browse the repository at this point in the history
* add CommandPanel component and CommandDictionary 
* pull dictionary logic out from SelectedCommand into SequenceEditor for reuse
* add command dictionary argument components
  • Loading branch information
duranb authored Dec 6, 2024
1 parent aab60d8 commit 2630a78
Show file tree
Hide file tree
Showing 30 changed files with 1,884 additions and 362 deletions.
5 changes: 5 additions & 0 deletions src/assets/inclusive-range.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions src/assets/ruler.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions src/components/sequencing/CommandPanel/CommandArg.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<svelte:options immutable={true} />

<script lang="ts">
import type { FswCommandArgument } from '@nasa-jpl/aerie-ampcs';
import {
isFswCommandArgumentBoolean,
isFswCommandArgumentEnum,
isFswCommandArgumentRepeat,
isNumberArg,
isStringArg,
} from '../../../utilities/codemirror/codemirror-utils';
import CommandBooleanArgDef from './CommandBooleanArgDef.svelte';
import CommandEnumArgDef from './CommandEnumArgDef.svelte';
import CommandNumberArgDef from './CommandNumberArgDef.svelte';
import CommandRepeatArgDef from './CommandRepeatArgDef.svelte';
import CommandStringArgDef from './CommandStringArgDef.svelte';
export let commandArgumentDefinition: FswCommandArgument;
</script>

<div>
{#if isFswCommandArgumentBoolean(commandArgumentDefinition)}
<CommandBooleanArgDef argDef={commandArgumentDefinition} />
{:else if isStringArg(commandArgumentDefinition)}
<CommandStringArgDef argDef={commandArgumentDefinition} />
{:else if isNumberArg(commandArgumentDefinition)}
<CommandNumberArgDef argDef={commandArgumentDefinition} />
{:else if isFswCommandArgumentEnum(commandArgumentDefinition)}
<CommandEnumArgDef argDef={commandArgumentDefinition} />
{:else if isFswCommandArgumentRepeat(commandArgumentDefinition)}
<CommandRepeatArgDef argDef={commandArgumentDefinition} />
{/if}
</div>
184 changes: 184 additions & 0 deletions src/components/sequencing/CommandPanel/CommandArg.svelte.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import type {
FswCommandArgument,
FswCommandArgumentBoolean,
FswCommandArgumentEnum,
FswCommandArgumentFloat,
FswCommandArgumentInteger,
FswCommandArgumentNumeric,
FswCommandArgumentRepeat,
FswCommandArgumentUnsigned,
FswCommandArgumentVarString,
} from '@nasa-jpl/aerie-ampcs';
import { cleanup, render } from '@testing-library/svelte';
import { keyBy } from 'lodash-es';
import { afterEach, describe, expect, it } from 'vitest';
import CommandArg from './CommandArg.svelte';

describe('CommandArg component', () => {
afterEach(() => {
cleanup();
});

it('Should render a boolean command argument', () => {
const booleanArgument: FswCommandArgumentBoolean = {
arg_type: 'boolean',
bit_length: 1,
default_value: 'Foo true',
description: 'test boolean arg',
format: {
false_str: 'Foo false',
true_str: 'Foo true',
},
name: 'Foo Boolean',
};
const { getByText } = render(CommandArg, { commandArgumentDefinition: booleanArgument });
expect(getByText(/Foo false/)).toBeDefined();
expect(getByText(/Foo true/)).toBeDefined();
});

it('Should render an enum command argument', () => {
const enumArgument: FswCommandArgumentEnum = {
arg_type: 'enum',
bit_length: 3,
default_value: 'Foo true',
description: 'test enum arg',
enum_name: 'Foo enum name',
name: 'Foo Enum',
range: ['bar', 'baz', 'buzz'],
};
const { getByDisplayValue } = render(CommandArg, { commandArgumentDefinition: enumArgument });
expect(getByDisplayValue(/bar/)).toBeDefined();
expect(getByDisplayValue(/baz/)).toBeDefined();
expect(getByDisplayValue(/buzz/)).toBeDefined();
});

describe('number command arguments', () => {
it('Should render a float command argument', () => {
const floatArgument: FswCommandArgumentFloat = {
arg_type: 'float',
bit_length: 3,
default_value: 10,
description: 'test float arg',
name: 'Foo Float',
range: {
max: 20,
min: 0,
},
units: 'foo',
};
const { getByText } = render(CommandArg, { commandArgumentDefinition: floatArgument });
expect(getByText(/0/)).toBeDefined();
expect(getByText(/20/)).toBeDefined();
});

it('Should render an integer command argument', () => {
const integerArgument: FswCommandArgumentInteger = {
arg_type: 'integer',
bit_length: 3,
default_value: 10,
description: 'test integer arg',
name: 'Foo Integer',
range: {
max: 20,
min: 0,
},
units: 'foo',
};
const { getByText } = render(CommandArg, { commandArgumentDefinition: integerArgument });
expect(getByText(/0/)).toBeDefined();
expect(getByText(/20/)).toBeDefined();
});

it('Should render an unsigned command argument', () => {
const unsignedArgument: FswCommandArgumentUnsigned = {
arg_type: 'unsigned',
bit_length: 3,
default_value: 10,
description: 'test unsigned arg',
name: 'Foo Unsigned',
range: {
max: 20,
min: 0,
},
units: 'foo',
};
const { getByText } = render(CommandArg, { commandArgumentDefinition: unsignedArgument });
expect(getByText(/0/)).toBeDefined();
expect(getByText(/20/)).toBeDefined();
});

it('Should render an unsigned command argument', () => {
const numericArgument: FswCommandArgumentNumeric = {
arg_type: 'numeric',
bit_length: 3,
default_value: 10,
description: 'test numeric arg',
name: 'Foo Numeric',
range: {
max: 20,
min: 0,
},
type: 'float',
units: 'foo',
};
const { getByText } = render(CommandArg, { commandArgumentDefinition: numericArgument });
expect(getByText(/0/)).toBeDefined();
expect(getByText(/20/)).toBeDefined();
});
});

it('Should render a string command argument', () => {
const stringArgument: FswCommandArgumentVarString = {
arg_type: 'var_string',
default_value: 'Foo string',
description: 'test string arg',
max_bit_length: 8,
name: 'Foo String',
prefix_bit_length: 2,
valid_regex: '',
};
const { getByText } = render(CommandArg, { commandArgumentDefinition: stringArgument });
expect(getByText(/test string arg/)).toBeDefined();
});

it('Should render a repeating command argument', () => {
const commandArguments: FswCommandArgument[] = [
{
arg_type: 'boolean',
bit_length: 1,
default_value: 'Foo true',
description: 'test boolean arg',
format: {
false_str: 'Foo false',
true_str: 'Foo true',
},
name: 'Boolean',
},
{
arg_type: 'var_string',
default_value: 'Foo string',
description: 'test string arg',
max_bit_length: 8,
name: 'String',
prefix_bit_length: 2,
valid_regex: '',
},
];
const repeatArgument: FswCommandArgumentRepeat = {
arg_type: 'repeat',
description: 'test repeating arg',
name: 'Foo Repeat',
prefix_bit_length: 2,
repeat: {
argumentMap: keyBy(commandArguments, 'name'),
arguments: commandArguments,
max: 4,
min: 1,
},
};
const { getByText } = render(CommandArg, { commandArgumentDefinition: repeatArgument });
expect(getByText(/test repeating arg/)).toBeDefined();
expect(getByText(/test boolean arg/)).toBeDefined();
expect(getByText(/test string arg/)).toBeDefined();
});
});
91 changes: 91 additions & 0 deletions src/components/sequencing/CommandPanel/CommandArgUnit.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<svelte:options immutable={true} />

<script lang="ts">
import InclusiveRangeIcon from '../../../assets/inclusive-range.svg?component';
import RulerIcon from '../../../assets/ruler.svg?component';
import { tooltip } from '../../../utilities/tooltip';
export let type: 'string' | 'boolean' | 'number' | 'enum' | 'repeat';
export let typeDisplay: string | undefined = undefined;
export let unit: string | undefined = undefined;
export let unitShortName: string | undefined = undefined;
export let range: { max: number | string; min: number | string } | undefined = undefined;
function isValidUnit(unitDisplay: string | undefined, unitShortNameDisplay: string | undefined) {
const unitToCheck = unitDisplay || unitShortNameDisplay;
if (unitToCheck) {
switch (unitToCheck.toLowerCase()) {
case 'n/a':
case 'none':
return false;
default:
return true;
}
}
}
</script>

<div class="unit-container">
{#if typeDisplay}
<div>
<div class="type faded"><RulerIcon /></div>
{typeDisplay}
</div>
{/if}
{#if range}
{#if type === 'boolean'}
<div>
{range.min}<span class="faded">|</span>{range.max}
</div>
{:else}
<div>
<div class="type faded"><InclusiveRangeIcon /></div>
{range.min}<span class="faded">-</span>{range.max}
</div>
{/if}
{/if}
{#if isValidUnit(unit, unitShortName)}
<div class="unit" use:tooltip={{ content: unit }}>
<div class="dot faded"></div>
{unitShortName || unit}
</div>
{/if}
</div>

<style>
.unit-container {
color: #000;
column-gap: 4px;
display: flex;
flex-flow: row;
}
.unit-container > div {
align-items: center;
background: var(--st-gray-10);
border: 1px solid var(--st-gray-15);
border-radius: 2px;
display: flex;
gap: 4px;
padding: 2px 4px;
}
.type {
align-items: center;
display: flex;
}
.faded {
opacity: 0.6;
}
.unit .dot {
border-color: var(--st-gray-100);
border-radius: 50%;
border-style: solid;
border-width: 1px;
height: 6px;
width: 6px;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, expect, it } from 'vitest';
import CommandArgUnit from './CommandArgUnit.svelte';

describe('CommandArgUnit component', () => {
afterEach(() => {
cleanup();
});

it('Should render units for a boolean command argument', () => {
const { getByText } = render(CommandArgUnit, {
range: { max: 'Foo true', min: 'Foo false' },
type: 'boolean',
typeDisplay: 'Foo boolean',
});
expect(getByText(/Foo false/)).toBeDefined();
expect(getByText(/Foo true/)).toBeDefined();
});

it('Should render a range for a command argument', () => {
const { getByText } = render(CommandArgUnit, {
range: { max: '24', min: '2' },
type: 'number',
typeDisplay: 'Foo number',
});
expect(getByText(/2/)).toBeDefined();
expect(getByText(/-/)).toBeDefined();
expect(getByText(/24/)).toBeDefined();
});

it('Should render units for a string command argument', () => {
const { getByText } = render(CommandArgUnit, {
type: 'string',
typeDisplay: 'Foo string',
});
expect(getByText(/Foo string/)).toBeDefined();
});

it('Should render short version of unit', () => {
const { getByText, queryByText } = render(CommandArgUnit, {
type: 'string',
typeDisplay: 'Foo string',
unit: 'bar baz',
unitShortName: 'foo',
});
expect(getByText('foo')).toBeDefined();
expect(queryByText('bar baz')).toBeNull();
});

it('Should render unit if no short version is provided', () => {
const { getByText } = render(CommandArgUnit, {
type: 'string',
typeDisplay: 'Foo string',
unit: 'bar baz',
});
expect(getByText('bar baz')).toBeDefined();
});
});
Loading

0 comments on commit 2630a78

Please sign in to comment.