fix: several merge messes

This commit is contained in:
jamesread 2026-06-28 22:15:46 +01:00
parent b823d8d21b
commit e63094e8e8
9 changed files with 136 additions and 43 deletions

View File

@ -52,6 +52,10 @@
<script setup>
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import {
choiceDisplayLabel,
syncStateFromModelValue
} from './choiceComboboxHelpers.js'
const props = defineProps({
id: {
@ -117,23 +121,23 @@ const filteredChoices = computed(() => {
})
})
watch(() => props.modelValue, () => {
watch([() => props.modelValue, () => props.choices], () => {
if (!isOpen.value) {
query.value = selectedLabel()
syncFromModelValue()
}
}, { immediate: true })
function choiceLabel(choice) {
return choice.title || choice.value
return choiceDisplayLabel(choice)
}
function selectedLabel() {
const match = props.choices.find(choice => choice.value === props.modelValue)
if (!match) {
return props.modelValue || ''
}
function syncFromModelValue() {
const next = syncStateFromModelValue(props.choices, props.modelValue)
query.value = next.query
return choiceLabel(match)
if (next.modelValue !== props.modelValue) {
emitValue(next.modelValue)
}
}
function openList() {
@ -144,7 +148,7 @@ function openList() {
function closeList() {
isOpen.value = false
query.value = selectedLabel()
syncFromModelValue()
}
function emitValue(value) {
@ -153,11 +157,15 @@ function emitValue(value) {
function selectChoice(choice) {
emitValue(choice.value)
closeList()
query.value = choiceLabel(choice)
isOpen.value = false
}
function handleFocus() {
query.value = isOpen.value ? query.value : selectedLabel()
if (!isOpen.value) {
syncFromModelValue()
}
openList()
}

View File

@ -0,0 +1,33 @@
export function findSelectedChoice (choices, modelValue) {
if (!modelValue) {
return null
}
return choices.find(choice => choice.value === modelValue) ?? null
}
export function choiceDisplayLabel (choice) {
return choice.title || choice.value
}
export function displayLabelForModelValue (choices, modelValue) {
const match = findSelectedChoice(choices, modelValue)
return match ? choiceDisplayLabel(match) : ''
}
export function normalizedModelValue (choices, modelValue) {
if (!modelValue) {
return ''
}
return findSelectedChoice(choices, modelValue) ? modelValue : ''
}
export function syncStateFromModelValue (choices, modelValue) {
const normalizedValue = normalizedModelValue(choices, modelValue)
return {
query: displayLabelForModelValue(choices, normalizedValue),
modelValue: normalizedValue
}
}

View File

@ -0,0 +1,40 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import {
displayLabelForModelValue,
normalizedModelValue,
syncStateFromModelValue
} from './choiceComboboxHelpers.js'
const choices = [
{ title: 'Production', value: 'prod' },
{ title: 'Staging', value: 'stage' }
]
test('displayLabelForModelValue returns the choice label for valid values', () => {
assert.equal(displayLabelForModelValue(choices, 'prod'), 'Production')
})
test('displayLabelForModelValue clears invalid enum values instead of echoing them', () => {
assert.equal(displayLabelForModelValue(choices, 'missing'), '')
})
test('normalizedModelValue keeps valid values and clears invalid ones', () => {
assert.equal(normalizedModelValue(choices, 'stage'), 'stage')
assert.equal(normalizedModelValue(choices, 'missing'), '')
assert.equal(normalizedModelValue(choices, ''), '')
})
test('syncStateFromModelValue clears invalid selections for closed-state sync', () => {
assert.deepEqual(syncStateFromModelValue(choices, 'missing'), {
query: '',
modelValue: ''
})
})
test('syncStateFromModelValue preserves valid selections for closed-state sync', () => {
assert.deepEqual(syncStateFromModelValue(choices, 'stage'), {
query: 'Staging',
modelValue: 'stage'
})
})

View File

@ -5,6 +5,8 @@ import {
getRootAndWait,
getActionButtons,
takeScreenshotOnFailure,
waitForArgumentFormPage,
waitForArgumentFormReady,
} from '../../lib/elements.js'
@ -46,11 +48,13 @@ describe('config: multipleDropdowns', function () {
await button.click()
// Wait for navigation to argument form page
await webdriver.wait(new Condition('wait for argument form page', async () => {
const url = await webdriver.getCurrentUrl()
return url.includes('/actionBinding/') && url.includes('/argumentForm')
}), 8000)
await waitForArgumentFormPage(8000)
await waitForArgumentFormReady(10000)
await webdriver.wait(new Condition('wait for choice comboboxes', async () => {
const boxes = await webdriver.findElements(By.css('main .choice-combobox'))
return boxes.length >= 2
}), 10000)
const comboboxes = await webdriver.findElements(By.css('main .choice-combobox'))

View File

@ -20,9 +20,7 @@ compile-x64-lin:
go env -u GOOS
compile-x64-win: windows-resources
go env -w GOOS=windows GOARCH=amd64
go build -o OliveTin.exe
go env -u GOOS GOARCH
GOOS=windows GOARCH=amd64 go build -o OliveTin.exe
windows-resources:
$(MAKE) -wC .. windows-resources

View File

@ -89,29 +89,39 @@ var envConfigTests = []struct {
}},
}
func setTestEnvInput(t *testing.T, input string) {
t.Helper()
if input != "" {
if err := os.Setenv("INPUT", input); err != nil {
t.Fatalf("Setenv INPUT: %v", err)
}
return
}
if err := os.Unsetenv("INPUT"); err != nil {
t.Fatalf("Unsetenv INPUT: %v", err)
}
}
func loadConfigFromYAML(t *testing.T, yamlStr string, cfg *Config) bool {
t.Helper()
k := koanf.New(".")
if err := k.Load(rawbytes.Provider([]byte(yamlStr)), yaml.Parser()); err != nil {
t.Errorf("Error loading YAML: %v", err)
return false
}
return unmarshalRoot(k, cfg)
}
func TestEnvInConfig(t *testing.T) {
for _, tt := range envConfigTests {
cfg := DefaultConfig()
setIfNotEmpty("INPUT", tt.input)
k := koanf.New(".")
err := k.Load(rawbytes.Provider([]byte(tt.yaml)), yaml.Parser())
if err != nil {
t.Errorf("Error loading YAML: %v", err)
continue
}
if !unmarshalRoot(k, cfg) {
setTestEnvInput(t, tt.input)
if !loadConfigFromYAML(t, tt.yaml, cfg) {
t.Errorf("Error unmarshalling config for env=%q", tt.input)
continue
}
field := tt.selector(cfg)
assert.Equal(t, tt.output, field,
"Unmarshaled config field doesn't match expected value: env=%q", tt.input)
os.Unsetenv("INPUT")
}
}
func setIfNotEmpty(key, val string) {
if val != "" {
os.Setenv(key, val)
}
}

View File

@ -24,8 +24,8 @@ normalize_msi_version() {
local raw="${1#v}"
raw="${raw%%-*}"
if [[ ! "${raw}" =~ ^[0-9]+(\.[0-9]+){0,3}$ ]]; then
echo "0.0.0"
return
echo "Invalid MSI version (expected major[.minor[.patch[.build]]]): ${1}" >&2
return 1
fi
local -a parts=()
IFS='.' read -r -a parts <<<"${raw}"
@ -43,7 +43,7 @@ if [[ -z "${VERSION}" ]]; then
echo "Could not determine release version; set VERSION explicitly" >&2
exit 1
fi
MSI_VERSION="$(normalize_msi_version "${VERSION}")"
MSI_VERSION="$(normalize_msi_version "${VERSION}")" || exit 1
STAGING="$(mktemp -d)"
APP_STAGING="$(mktemp -d)"

View File

@ -37,14 +37,14 @@ normalize_windows_version() {
}
resolve_version() {
if [[ -n "${VERSION:-}" ]]; then
echo "${VERSION}"
return
fi
if [[ $# -gt 0 && -n "${1:-}" ]]; then
echo "${1}"
return
fi
if [[ -n "${VERSION:-}" ]]; then
echo "${VERSION}"
return
fi
if git -C "${REPO_ROOT}" describe --tags --abbrev=0 >/dev/null 2>&1; then
git -C "${REPO_ROOT}" describe --tags --abbrev=0
return

View File

@ -28,7 +28,7 @@ checksums_path="${DIST_DIR}/checksums.txt"
new_checksum="$(cd "${DIST_DIR}" && sha256sum "${MSI_NAME}")"
if [[ -f "${checksums_path}" ]] && grep -qF " ${MSI_NAME}" "${checksums_path}"; then
tmp="$(mktemp)"
grep -vF " ${MSI_NAME}" "${checksums_path}" > "${tmp}"
grep -vF " ${MSI_NAME}" "${checksums_path}" > "${tmp}" || true
printf '%s\n' "${new_checksum}" >> "${tmp}"
mv "${tmp}" "${checksums_path}"
elif [[ -f "${checksums_path}" ]]; then