Przeglądaj źródła

fix: several merge messes

jamesread 5 dni temu
rodzic
commit
e63094e8e8

+ 20 - 12
frontend/resources/vue/components/ChoiceCombobox.vue

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

+ 33 - 0
frontend/resources/vue/components/choiceComboboxHelpers.js

@@ -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
+  }
+}

+ 40 - 0
frontend/resources/vue/components/choiceComboboxHelpers.test.mjs

@@ -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'
+  })
+})

+ 9 - 5
integration-tests/tests/multipleDropdowns/multipleDropdowns.js

@@ -5,6 +5,8 @@ import {
   getRootAndWait,
   getRootAndWait,
   getActionButtons,
   getActionButtons,
   takeScreenshotOnFailure,
   takeScreenshotOnFailure,
+  waitForArgumentFormPage,
+  waitForArgumentFormReady,
 } from '../../lib/elements.js'
 } from '../../lib/elements.js'
 
 
 
 
@@ -46,11 +48,13 @@ describe('config: multipleDropdowns', function () {
 
 
     await button.click()
     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'))
     const comboboxes = await webdriver.findElements(By.css('main .choice-combobox'))
 
 

+ 1 - 3
service/Makefile

@@ -20,9 +20,7 @@ compile-x64-lin:
 	go env -u GOOS
 	go env -u GOOS
 
 
 compile-x64-win: windows-resources
 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:
 windows-resources:
 	$(MAKE) -wC .. windows-resources
 	$(MAKE) -wC .. windows-resources

+ 25 - 15
service/internal/config/config_reloader_test.go

@@ -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) {
 func TestEnvInConfig(t *testing.T) {
 	for _, tt := range envConfigTests {
 	for _, tt := range envConfigTests {
 		cfg := DefaultConfig()
 		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)
 			t.Errorf("Error unmarshalling config for env=%q", tt.input)
 			continue
 			continue
 		}
 		}
 		field := tt.selector(cfg)
 		field := tt.selector(cfg)
 		assert.Equal(t, tt.output, field,
 		assert.Equal(t, tt.output, field,
 			"Unmarshaled config field doesn't match expected value: env=%q", tt.input)
 			"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)
 	}
 	}
 }
 }

+ 3 - 3
var/windows/build-msi.sh

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

+ 4 - 4
var/windows/generate-resources.sh

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

+ 1 - 1
var/windows/upload-msi-release.sh

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