diff --git a/App/Sources/App/ContentViewModel.swift b/App/Sources/App/ContentViewModel.swift index aa8136e..2b83a6c 100644 --- a/App/Sources/App/ContentViewModel.swift +++ b/App/Sources/App/ContentViewModel.swift @@ -30,6 +30,7 @@ final class ContentViewModel { .metadata(\.displayName, "Float Array Example") let intArrayVariable = ConfigVariable(key: "int_array", defaultValue: [1, 2, 4, 8, 16, 32]) .metadata(\.displayName, "Int Array Example") + .metadata(\.isEditable, false) let stringArrayVariable = ConfigVariable( key: "string_array", defaultValue: ["Thom", "Jonny", "Ed", "Colin", "Phil"] diff --git a/Sources/DevConfiguration/Editor/Config Variable Detail/ConfigVariableDetailViewModel.swift b/Sources/DevConfiguration/Editor/Config Variable Detail/ConfigVariableDetailViewModel.swift index eb5d4a0..b227b98 100644 --- a/Sources/DevConfiguration/Editor/Config Variable Detail/ConfigVariableDetailViewModel.swift +++ b/Sources/DevConfiguration/Editor/Config Variable Detail/ConfigVariableDetailViewModel.swift @@ -49,7 +49,7 @@ final class ConfigVariableDetailViewModel: ConfigVariableDetailViewModeling { self.variableTypeName = registeredVariable.destinationTypeName self.metadataEntries = registeredVariable.metadata.displayTextEntries self.isSecret = registeredVariable.isSecret - self.editorControl = registeredVariable.editorControl + self.editorControl = registeredVariable.isEditable ? registeredVariable.editorControl : nil if let content = document.override(forKey: registeredVariable.key) { self.overrideText = content.editableString diff --git a/Sources/DevConfiguration/Metadata/IsEditableMetadataKey.swift b/Sources/DevConfiguration/Metadata/IsEditableMetadataKey.swift new file mode 100644 index 0000000..5b9f6b4 --- /dev/null +++ b/Sources/DevConfiguration/Metadata/IsEditableMetadataKey.swift @@ -0,0 +1,25 @@ +// +// IsEditableMetadataKey.swift +// DevConfiguration +// +// Created by Prachi Gauriar on 3/17/2026. +// + +import Foundation + +/// The metadata key indicating whether a variable is editable in the variable editor. +private struct IsEditableMetadataKey: ConfigVariableMetadataKey { + static let defaultValue = true + static let keyDisplayText = localizedString("isEditableMetadata.keyDisplayText") +} + + +extension ConfigVariableMetadata { + /// Whether the configuration variable is editable in the variable editor. + /// + /// When `false`, the editor UI hides the override controls for this variable. Defaults to `true`. + public var isEditable: Bool { + get { self[IsEditableMetadataKey.self] } + set { self[IsEditableMetadataKey.self] = newValue } + } +} diff --git a/Sources/DevConfiguration/Resources/Localizable.xcstrings b/Sources/DevConfiguration/Resources/Localizable.xcstrings index cfc06f1..59f9c35 100644 --- a/Sources/DevConfiguration/Resources/Localizable.xcstrings +++ b/Sources/DevConfiguration/Resources/Localizable.xcstrings @@ -361,6 +361,16 @@ } } }, + "isEditableMetadata.keyDisplayText" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Is Editable" + } + } + } + }, "requiresRelaunchMetadata.keyDisplayText" : { "localizations" : { "en" : { diff --git a/Tests/DevConfigurationTests/Unit Tests/Editor/Config Variable Detail/ConfigVariableDetailViewModelTests.swift b/Tests/DevConfigurationTests/Unit Tests/Editor/Config Variable Detail/ConfigVariableDetailViewModelTests.swift index ba7ea2d..8d2a528 100644 --- a/Tests/DevConfigurationTests/Unit Tests/Editor/Config Variable Detail/ConfigVariableDetailViewModelTests.swift +++ b/Tests/DevConfigurationTests/Unit Tests/Editor/Config Variable Detail/ConfigVariableDetailViewModelTests.swift @@ -86,6 +86,27 @@ struct ConfigVariableDetailViewModelTests: RandomValueGenerating { } + @Test + mutating func initSetsEditorControlToNilWhenNotEditable() { + // set up with isEditable = false + var metadata = ConfigVariableMetadata() + metadata.isEditable = false + + let variable = randomRegisteredVariable( + metadata: metadata, + editorControl: .textField + ) + + let document = makeDocument(registeredVariables: [variable]) + + // exercise + let viewModel = makeViewModel(document: document, registeredVariable: variable) + + // expect editorControl is nil because the variable is not editable + #expect(viewModel.editorControl == nil) + } + + @Test mutating func initUsesKeyDescriptionWhenDisplayNameIsNil() { // set up with no display name metadata diff --git a/Tests/DevConfigurationTests/Unit Tests/Metadata/IsEditableMetadataKeyTests.swift b/Tests/DevConfigurationTests/Unit Tests/Metadata/IsEditableMetadataKeyTests.swift new file mode 100644 index 0000000..3072b55 --- /dev/null +++ b/Tests/DevConfigurationTests/Unit Tests/Metadata/IsEditableMetadataKeyTests.swift @@ -0,0 +1,42 @@ +// +// IsEditableMetadataKeyTests.swift +// DevConfiguration +// +// Created by Prachi Gauriar on 3/17/2026. +// + +import Testing + +@testable import DevConfiguration + +struct IsEditableMetadataKeyTests { + @Test + func isEditableDefaultsToTrueAndStoresAndRetrievesValue() { + // set up + var metadata = ConfigVariableMetadata() + + // expect that unset isEditable returns true + #expect(metadata.isEditable) + + // exercise + metadata.isEditable = false + + // expect that the value is stored and retrieved correctly + #expect(!metadata.isEditable) + } + + + @Test + func isEditableDisplayTextShowsValue() throws { + // set up + var metadata = ConfigVariableMetadata() + + // exercise + metadata.isEditable = false + + // expect that displayTextEntries contains the is editable entry with a localized key + let entries = metadata.displayTextEntries + let entry = try #require(entries.first { $0.value == "false" }) + #expect(entry.key != "isEditableMetadata.keyDisplayText") + } +}