chore: Fix coderabbit warnings

This commit is contained in:
jamesread 2026-07-06 11:39:43 +01:00
parent e24ae6265b
commit 6077c63cfd
6 changed files with 107 additions and 40 deletions

View File

@ -9,7 +9,7 @@
</button> </button>
</div> </div>
<fieldset class="choice-checklist-fieldset"> <fieldset class="choice-checklist-fieldset">
<legend class="visually-hidden">{{ name }}</legend> <legend class="visually-hidden">{{ label || name }}</legend>
<label <label
v-for="(choice, index) in choices" v-for="(choice, index) in choices"
:key="choice.value" :key="choice.value"
@ -28,9 +28,12 @@
<input <input
:id="`${id}-value`" :id="`${id}-value`"
:name="name" :name="name"
type="hidden" type="text"
class="visually-hidden choice-checklist-value"
:value="modelValue" :value="modelValue"
:required="required && modelValue === ''" :required="required"
tabindex="-1"
aria-hidden="true"
/> />
</div> </div>
</template> </template>
@ -54,6 +57,10 @@ const props = defineProps({
type: String, type: String,
required: true required: true
}, },
label: {
type: String,
default: ''
},
choices: { choices: {
type: Array, type: Array,
required: true required: true
@ -119,7 +126,7 @@ function selectNone() {
border: none; border: none;
display: grid; display: grid;
gap: 0.5em 1em; gap: 0.5em 1em;
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
margin: 0; margin: 0;
padding: 0; padding: 0;
} }

View File

@ -15,6 +15,7 @@ const choices = [
test('parseChecklistValue splits comma-delimited values', () => { test('parseChecklistValue splits comma-delimited values', () => {
assert.deepEqual(parseChecklistValue('documents,photos'), ['documents', 'photos']) assert.deepEqual(parseChecklistValue('documents,photos'), ['documents', 'photos'])
assert.deepEqual(parseChecklistValue('documents, photos'), ['documents', 'photos'])
assert.deepEqual(parseChecklistValue(''), []) assert.deepEqual(parseChecklistValue(''), [])
}) })

View File

@ -26,8 +26,8 @@
@update:model-value="handleChoiceUpdate(arg, $event)" /> @update:model-value="handleChoiceUpdate(arg, $event)" />
<ChoiceChecklist v-else-if="arg.type === 'checklist'" :id="arg.name" :name="arg.name" <ChoiceChecklist v-else-if="arg.type === 'checklist'" :id="arg.name" :name="arg.name"
:choices="arg.choices" :model-value="getArgumentValue(arg)" :required="arg.required" :label="arg.title" :choices="arg.choices" :model-value="getArgumentValue(arg)" :required="arg.required"
@update:model-value="handleChecklistUpdate(arg, $event)" /> @update:model-value="handleChoiceUpdate(arg, $event)" />
<component v-else :is="getInputComponent(arg)" :id="arg.name" :name="arg.name" <component v-else :is="getInputComponent(arg)" :id="arg.name" :name="arg.name"
:value="(arg.type === 'checkbox' || arg.type === 'confirmation') ? undefined : getArgumentValue(arg)" :value="(arg.type === 'checkbox' || arg.type === 'confirmation') ? undefined : getArgumentValue(arg)"
@ -145,8 +145,6 @@ async function setup() {
} else { } else {
argValues.value[arg.name] = false argValues.value[arg.name] = false
} }
} else if (arg.type === 'checklist') {
argValues.value[arg.name] = paramValue !== null ? paramValue : arg.defaultValue || ''
} else { } else {
argValues.value[arg.name] = paramValue !== null ? paramValue : arg.defaultValue || '' argValues.value[arg.name] = paramValue !== null ? paramValue : arg.defaultValue || ''
} }
@ -253,12 +251,6 @@ function getValidationElement(arg) {
return document.getElementById(arg.name) return document.getElementById(arg.name)
} }
function handleChecklistUpdate(arg, value) {
argValues.value[arg.name] = value
updateUrlWithArg(arg.name, value)
validateArgument(arg, value)
}
function handleChoiceUpdate(arg, value) { function handleChoiceUpdate(arg, value) {
argValues.value[arg.name] = value argValues.value[arg.name] = value
updateUrlWithArg(arg.name, value) updateUrlWithArg(arg.name, value)
@ -503,6 +495,11 @@ async function handleSubmit(event) {
return return
} }
if (Object.keys(formErrors.value).length > 0) {
console.log('argument form has validation errors')
return
}
const argvs = getArgumentValues() const argvs = getArgumentValues()
console.log('argument form has elements that passed validation') console.log('argument form has elements that passed validation')

View File

@ -27,9 +27,9 @@ async function submitChecklistForm() {
await submitButton.click() await submitButton.click()
} }
async function waitForTerminalOutput(expectedValue) { async function pollTerminal(matcher, timeoutMs = DEFAULT_UI_WAIT_MS) {
await webdriver.wait( await webdriver.wait(
new Condition(`wait for checklist value ${expectedValue} in output`, async () => { new Condition('wait for terminal output', async () => {
try { try {
const terminalReady = await webdriver.executeScript(` const terminalReady = await webdriver.executeScript(`
return !!(window.terminal && window.terminal.getBufferAsString); return !!(window.terminal && window.terminal.getBufferAsString);
@ -43,42 +43,34 @@ async function waitForTerminalOutput(expectedValue) {
return false return false
} }
return output.trim().includes(`Selected segments: ${expectedValue}`) return matcher(output.trim())
} catch (e) { } catch (e) {
return false return false
} }
}), }),
timeoutMs
)
}
async function waitForTerminalOutput(expectedValue) {
await pollTerminal(
(output) => output.includes(`Selected segments: ${expectedValue}`),
DEFAULT_UI_WAIT_MS DEFAULT_UI_WAIT_MS
) )
} }
async function waitForTerminalOutputPattern(pattern) { async function waitForTerminalOutputPattern(pattern) {
await webdriver.wait( await pollTerminal(
new Condition(`wait for terminal output matching ${pattern}`, async () => { (output) => pattern.test(output),
try {
const terminalReady = await webdriver.executeScript(`
return !!(window.terminal && window.terminal.getBufferAsString);
`)
if (!terminalReady) {
return false
}
const output = await getTerminalBuffer()
if (!output) {
return false
}
return pattern.test(output.trim())
} catch (e) {
return false
}
}),
10000 10000
) )
} }
async function getCheckboxByValueIndex(index) { async function getCheckboxByValueIndex(index) {
return await webdriver.findElement(By.id(`segments-${index}`)) const checkboxes = await webdriver.findElements(
By.css('.choice-checklist-item input[type="checkbox"]')
)
return checkboxes[index]
} }
describe('config: checklist', function () { describe('config: checklist', function () {
@ -118,8 +110,8 @@ describe('config: checklist', function () {
await selectNone.click() await selectNone.click()
await webdriver.sleep(300) await webdriver.sleep(300)
const hidden = await webdriver.findElement(By.id('segments-value')) const valueInput = await webdriver.findElement(By.css('.choice-checklist > input'))
expect(await hidden.getAttribute('value')).to.equal('') expect(await valueInput.getAttribute('value')).to.equal('')
await submitChecklistForm() await submitChecklistForm()
await waitForLogsPage() await waitForLogsPage()

View File

@ -34,6 +34,10 @@ func (cfg *Config) Sanitize() {
if err := cfg.validateReservedActionArgumentNames(); err != nil { if err := cfg.validateReservedActionArgumentNames(); err != nil {
log.Fatalf("%v", err) log.Fatalf("%v", err)
} }
if err := cfg.validateChecklistChoiceValues(); err != nil {
log.Fatalf("%v", err)
}
} }
func (cfg *Config) validateReservedActionArgumentNames() error { func (cfg *Config) validateReservedActionArgumentNames() error {
@ -60,6 +64,49 @@ func (action *Action) validateReservedArgumentNames() error {
return nil return nil
} }
func (cfg *Config) validateChecklistChoiceValues() error {
for _, action := range cfg.Actions {
if err := action.validateChecklistChoiceValues(); err != nil {
return err
}
}
return nil
}
func (action *Action) validateChecklistChoiceValues() error {
if action == nil {
return nil
}
for _, arg := range action.Arguments {
if err := validateChecklistChoicesForArgument(action.Title, arg); err != nil {
return err
}
}
return nil
}
func validateChecklistChoicesForArgument(actionTitle string, arg ActionArgument) error {
if arg.Type != "checklist" {
return nil
}
for _, choice := range arg.Choices {
if strings.Contains(choice.Value, ",") {
return fmt.Errorf(
`action %q argument %q choice value %q must not contain commas`,
actionTitle,
arg.Name,
choice.Value,
)
}
}
return nil
}
func (cfg *Config) sanitizeDashboardsForInlineActions() { func (cfg *Config) sanitizeDashboardsForInlineActions() {
for _, dashboard := range cfg.Dashboards { for _, dashboard := range cfg.Dashboards {
cfg.sanitizeDashboardComponentForInlineActions(dashboard) cfg.sanitizeDashboardComponentForInlineActions(dashboard)

View File

@ -271,3 +271,26 @@ func TestValidateUniqueLocalUserAPIKeys(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
} }
func TestValidateChecklistChoiceValuesRejectsCommas(t *testing.T) {
t.Parallel()
c := DefaultConfig()
c.Actions = append(c.Actions, &Action{
Title: "Checklist commas",
Shell: "true",
Arguments: []ActionArgument{
{
Name: "segments",
Type: "checklist",
Choices: []ActionArgumentChoice{
{Value: "kitchen,bedroom"},
},
},
},
})
err := c.validateChecklistChoiceValues()
require.Error(t, err)
assert.Contains(t, err.Error(), `choice value "kitchen,bedroom" must not contain commas`)
}