main
py 158 lines 8.49 KB
Raw
1 from pathlib import Path
2
3
4 PROJECT_ROOT = Path(__file__).resolve().parents[1]
5
6
7 def read(*parts: str) -> str:
8 return PROJECT_ROOT.joinpath(*parts).read_text(encoding="utf-8")
9
10
11 def test_model_config_text_buttons_have_shared_primitive() -> None:
12 buttons_css = read("webui", "css", "buttons.css")
13 preset_modal = read("plugins", "_model_config", "webui", "main.html")
14 overview = read("plugins", "_model_config", "webui", "preset-overview.html")
15
16 assert ".text-button {" in buttons_css
17 assert "appearance: none;" in buttons_css
18 assert ".text-button:hover:not(:disabled)" in buttons_css
19 assert ".text-button .material-symbols-outlined" in buttons_css
20 assert 'class="text-button"' in preset_modal
21 assert 'class="model-preset-actions"' in overview
22
23
24 def test_model_preset_rows_keep_stable_identity_after_middle_delete() -> None:
25 preset_modal = read("plugins", "_model_config", "webui", "main.html")
26 preset_store = read("plugins", "_model_config", "webui", "model-config-store.js")
27
28 assert 'x-data="$store.modelConfig.createPresetEditor($store.modelConfig.presetEditorInitialName)"' in preset_modal
29 assert ':key="preset._key"' in preset_modal
30 assert 'x-model.number="selectedKey"' in preset_modal
31 assert "createPresetEditor(initialName = '')" in preset_store
32 assert "preparePresetEditor($store.chats?.selected || '')" in preset_modal
33 assert "_key: nextPresetKey++" in preset_store
34 assert "removeSelectedPreset()" in preset_store
35 assert "selectedKey" in preset_store
36 assert ':key="idx"' not in preset_modal
37 assert "JSON.parse(JSON.stringify" not in preset_modal
38 assert "presets.splice" not in preset_store
39
40
41 def test_model_preset_editor_can_reset_to_bundled_defaults() -> None:
42 preset_modal = read("plugins", "_model_config", "webui", "main.html")
43 preset_store = read("plugins", "_model_config", "webui", "model-config-store.js")
44
45 assert '$confirmClick($event, () => resetPresets())' in preset_modal
46 assert "Restore bundled presets" in preset_modal
47 assert "async resetPresets()" in preset_store
48 assert "if (!await store.resetGlobalPresets()) return;" in preset_store
49 assert "this.refreshPresets();" in preset_store
50
51
52 def test_default_preset_is_locked_and_shared_overview_is_reused() -> None:
53 preset_modal = read("plugins", "_model_config", "webui", "main.html")
54 config_modal = read("plugins", "_model_config", "webui", "config.html")
55 settings_summary = read("plugins", "_model_config", "webui", "models-summary.html")
56 helper = read("plugins", "_model_config", "helpers", "model_config.py")
57
58 assert '<template x-if="canRenameSelected">' in preset_modal
59 assert "get canRenameSelected()" in read(
60 "plugins", "_model_config", "webui", "model-config-store.js"
61 )
62 assert "Default cannot be renamed or deleted." not in preset_modal
63 assert ":readonly=" not in preset_modal
64 assert "The Default preset cannot be deleted or renamed." in helper
65 assert "preserveImplicitDefaults" in read(
66 "plugins", "_model_config", "webui", "model-config-store.js"
67 )
68 component_path = "/plugins/_model_config/webui/preset-overview.html"
69 assert component_path in config_modal
70 assert component_path in settings_summary
71 assert "Per-project / agent" in read(
72 "plugins", "_model_config", "webui", "preset-overview.html"
73 )
74
75
76 def test_preset_editor_uses_standard_modal_footer_buttons() -> None:
77 preset_modal = read("plugins", "_model_config", "webui", "main.html")
78 api_keys_modal = read("plugins", "_model_config", "webui", "api-keys.html")
79
80 for content in (preset_modal, api_keys_modal):
81 assert '<div class="modal-footer" data-modal-footer>' in content
82 assert 'class="btn btn-ok"' in content
83 assert 'class="btn btn-cancel"' in content
84
85 assert "preset-editor-footer" not in preset_modal
86
87
88 def test_preset_editor_nests_one_conditional_vision_selector_in_main() -> None:
89 preset_modal = read("plugins", "_model_config", "webui", "main.html")
90 model_field = read("plugins", "_model_config", "webui", "model-field.html")
91 preset_overview = read("plugins", "_model_config", "webui", "preset-overview.html")
92 preset_store = read("plugins", "_model_config", "webui", "model-config-store.js")
93
94 main_start = preset_modal.index('<div class="section-title">Main Model</div>')
95 utility_start = preset_modal.index('<div class="section-title">Utility Model</div>')
96 selector_start = preset_modal.index('class="vision-sidecar-selector"')
97 supports_start = model_field.index('<div class="field-title">Supports Vision</div>')
98 override_start = model_field.index('<div class="field-title">Use separate Vision Model</div>')
99 context_start = model_field.index('<div class="field-title">Context window size</div>')
100 advanced_start = model_field.index('<!-- Advanced Settings (collapsed by default) -->')
101 vision_advanced_start = model_field.index('<template x-if="modelType === \'vision\'">')
102 vision_advanced_end = model_field.index('<!-- Utility-specific: ctx_input slider -->')
103 vision_advanced = model_field[vision_advanced_start:vision_advanced_end]
104
105 assert '<div class="section-title">Vision Model</div>' not in preset_modal
106 assert preset_modal.count('class="vision-sidecar-selector"') == 1
107 assert main_start < selector_start < utility_start
108 assert '!selectedPreset.chat.vision || selectedPreset.vision.override_main' in preset_modal
109 assert "get visionModel() { return selectedPreset.vision; }" in preset_modal
110 assert "modelType: 'vision'" in preset_modal
111 assert preset_modal.count("apiKeyMode: 'store'") == 4
112 assert "margin: 0.75rem 0 0;" in preset_modal
113 assert "padding: 0.25rem 0 0;" in preset_modal
114 assert "border-left: 2px solid var(--color-border);" not in preset_modal
115 assert "Use separate Vision Model" in model_field
116 assert supports_start < override_start < context_start < advanced_start
117 assert "When disabled, vision_load uses this model's native vision." in model_field
118 assert "When enabled, vision_load uses the preset's Vision Model" not in model_field
119 assert 'x-model="visionModel.override_main"' in model_field
120 assert '<div class="advanced-section" x-data="{ advOpen: false }">' in model_field
121 assert advanced_start < vision_advanced_start
122 assert model_field.count('<div class="field-title">Timeout (seconds)</div>') == 1
123 assert model_field.count('<div class="field-title">Max tokens</div>') == 1
124 assert 'x-model.number="model.timeout"' in vision_advanced
125 assert 'x-model.number="model.max_tokens"' in vision_advanced
126 assert "How long to wait for the Vision Model." in vision_advanced
127 assert "Maximum output tokens for each delegated vision call." in vision_advanced
128 assert "Agent Editor &gt; Advanced &gt; Prompt files" in vision_advanced
129 assert "fw.vision_load.md" in vision_advanced
130 assert "delegated_system" not in model_field
131 assert "vision prompt" not in model_field[:vision_advanced_start].lower()
132 assert "timeout: 300" in preset_store
133 assert "max_tokens: 2000" in preset_store
134 assert "value?.timeout ?? kwargs.timeout ?? 300" in preset_store
135 assert "value?.max_tokens ?? kwargs.max_tokens ?? 2000" in preset_store
136 assert "delete kwargs.timeout;" in preset_store
137 assert "delete kwargs.max_tokens;" in preset_store
138 assert "_kwargs_text: kwargsToText(vision.kwargs)" in preset_store
139 assert "model-preset-row-nested" in preset_overview
140 assert "model.title === 'Vision'" in preset_overview
141 assert preset_overview.count("model.title !== 'Vision'") == 2
142 assert '<span class="model-preset-role">Vision override</span>' in preset_overview
143 assert "'model-preset-identity-vision': model.title === 'Vision'" in preset_overview
144 assert "grid-column: 3;" in preset_overview
145 assert "padding-top: var(--spacing-xs);" in preset_overview
146 assert "margin-left: 2rem;" not in preset_overview
147 assert "border-left: 1px solid var(--color-border);" not in preset_overview
148 assert "if (slotKey === 'vision') config[sectionKey] = {};" in preset_store
149 assert "['chat', 'vision', 'utility']" in preset_store
150
151
152 def test_plugin_settings_reset_is_explicit_and_does_not_capture_toast_early() -> None:
153 settings_modal = read("webui", "components", "plugins", "plugin-settings.html")
154 settings_store = read("webui", "components", "plugins", "plugin-settings-store.js")
155
156 assert "Reset to default" in settings_modal
157 assert "const justToast = globalThis.justToast" not in settings_store
158 assert 'globalThis.justToast?.("Settings reset to default.", "info")' in settings_store