Skip to content

Commit b32f6a9

Browse files
donislawdevclaude
andcommitted
gui, guard: nine things reported from the running window
A setting chosen from a list opens on its own default instead of on an extra entry reading "not stated - pdf". That entry existed so a default nobody picked could be told from a value somebody chose, and its written reason was untouchable rule 5. Measured at both ends and neither end read it: an ICO run with embed left alone and one with embed=bmp produce the same bytes and the same manifest, both writing embed: bmp with the word defaulted in neither, and a preset's defaulted is built from the parameters a preset declares, which the global format flag is not one of. One correction to that measurement, found by the guard comparing the two surfaces: defaulted does not carry it, but the parameters block does once a value is given. Settled on the screen's side, since the menu shows the word pdf to whoever is looking at it. The files are unchanged. Boxes somebody types into keep the old promise. Leaving a preset's limit empty still records it as ours rather than theirs. Every list of formats draws the file kind pictures, decided in parts.NewChooser rather than at each call site - two of the three lists had gone twenty days without them because the assignment was a line somebody had to remember. Gated on the whole set of registered formats, since a subset is the case that must not get them: ICO's embed offers two pictures and would draw one icon twice. "Add files inside" appears only under a format that holds files. The reason written beside that code said asking would put a rule about formats in the window - false, since Container is a declared field on a descriptor. Rows already typed stay on screen when the format changes, so nothing is thrown away and a refusal still has a field to point at. Four fields explain themselves better, including the seed, which now says that 0 is the seed a run uses when nobody asks for another one rather than a request for random files. parts.RowWidthFor is the one home of a list row's arithmetic, and menuWidth takes the wider of the closed box and the row. Two changes met on the preset screen: pictures widen a row, and dropping "not stated - pdf" removed the longest string that had been holding the box wide enough to hide it. Every one of the twenty formats came out cut off, and the guard said so before anything here changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 1f098f5 commit b32f6a9

65 files changed

Lines changed: 1087 additions & 743 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,38 @@ because it turns other people's test suites red.
282282

283283
### Changed
284284

285+
- The window no longer offers to put files inside a format that holds none.
286+
"Add files inside" now appears under ZIP and TAR.GZ, and nowhere else. It used
287+
to appear under every batch, so a PNG batch carried a button whose only
288+
destination was a refusal. Rows you already filled in stay on screen if you
289+
change the format afterwards, so nothing you typed disappears and the refusal
290+
still has a field to point at.
291+
292+
- Every list of file formats in the window now draws the same small picture
293+
beside each value. One of the three lists had them and two did not, so the
294+
same twenty formats looked like different kinds of list depending on which tab
295+
you were on.
296+
297+
- A setting chosen from a list now opens on its own default instead of on an
298+
extra entry reading "not stated - pdf". The extra entry existed so that a
299+
default nobody picked could be told from a value somebody chose. Measured on
300+
both surfaces, nothing downstream ever read that difference for a setting
301+
drawn as a list: a run leaving an ICO's embed alone and a run asking for
302+
embed=bmp produce the same bytes and the same manifest, and the preset block's
303+
defaulted list is built from a preset's own parameters, which the format is
304+
not one of. Boxes you type into are unchanged - leaving a preset's limit empty
305+
still records it as ours rather than yours.
306+
307+
One visible consequence: a run started from the Presets tab now records the
308+
format in the manifest's preset parameters, because the screen states it. The
309+
files are the same.
310+
311+
- Four fields explain themselves better. The format list dropped its second
312+
sentence, which described the list you were already looking at. "File names"
313+
and "Batch name" say what they are for and what they change. The seed says
314+
what 0 means - it is the seed a run uses when nobody asks for another one, and
315+
it is not a request for random files, which this tool never produces.
316+
285317
- Two refusals are worded differently. Nothing about what is accepted has
286318
changed - only the sentences.
287319

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package guard
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/donislawdev/TestingFilesGenerator/internal/format"
8+
"github.com/donislawdev/TestingFilesGenerator/internal/gui/text"
9+
"github.com/donislawdev/TestingFilesGenerator/internal/gui/window"
10+
"github.com/donislawdev/TestingFilesGenerator/internal/recipe"
11+
)
12+
13+
// Only a format that holds other files offers to put files in it.
14+
//
15+
// Every batch used to carry an "Add files inside" button, whatever it was going
16+
// to produce, and a PNG batch is where the owner saw it on 2026-08-27. The
17+
// reason written beside the code was that asking a format whether it holds
18+
// files would put a rule about formats into the window. That premise is false:
19+
// Container is a DECLARED field on a descriptor, sitting beside the properties
20+
// this same screen already draws straight from the declaration, so asking it is
21+
// the opposite of inventing a rule.
22+
//
23+
// Asked of the registry rather than of a list written here, so a format
24+
// registered tomorrow is covered on the day it is registered - which is the
25+
// same reason the menu itself is built from format.IDs().
26+
func TestOnlyAFormatThatHoldsFilesOffersToPutFilesInIt(t *testing.T) {
27+
screen := window.NewRecipe(newFakeHost(t))
28+
body := screen.Object()
29+
30+
holders, plain := 0, 0
31+
for _, d := range format.All() {
32+
chooserIn(t, screen.Fields(), recipe.TargetAddress(1, recipe.KeyFormat)).SetSelected(d.ID)
33+
34+
offered := buttonNamed(body, text.ButtonAddContents()) != nil
35+
switch {
36+
case d.Container && !offered:
37+
t.Errorf("%s holds other files and its batch offers no way to say what they are", d.ID)
38+
case !d.Container && offered:
39+
t.Errorf("%s holds no other files and its batch offers to put files in it, "+
40+
"which leads nowhere but a refusal", d.ID)
41+
}
42+
if d.Container {
43+
holders++
44+
} else {
45+
plain++
46+
}
47+
}
48+
49+
// Both sides have to have happened. A build registering only containers -
50+
// or only plain formats - would satisfy every branch above without either
51+
// half of the rule being exercised.
52+
if holders == 0 || plain == 0 {
53+
t.Fatalf("this guard saw %d format(s) that hold files and %d that do not, "+
54+
"so one half of the rule went unchecked", holders, plain)
55+
}
56+
t.Logf("%d format(s) offer to hold files, %d do not", holders, plain)
57+
}
58+
59+
// What somebody already typed into an archive stays on screen when the format
60+
// changes under it.
61+
//
62+
// This is the half of the decision worth being careful about, and it is the
63+
// owner's, taken on 2026-08-27 with both of the alternatives named. Dropping
64+
// the rows would throw away typed work on the strength of one menu press, which
65+
// is the silence untouchable rule 6 forbids. Hiding them while still sending
66+
// them would put the run in the state folding the batches away was allowed to
67+
// close on condition of avoiding: the engine refuses over a value, the screen
68+
// has no box to mark, and the button reads as having done nothing.
69+
//
70+
// So the rows outlive the format and the refusal has somewhere to land. The way
71+
// out of it stays visible too - remove them, or put the format back.
72+
func TestContentsAlreadyTypedStayOnScreenWhenTheFormatChanges(t *testing.T) {
73+
screen := window.NewRecipe(newFakeHost(t))
74+
body := screen.Object()
75+
76+
at := recipe.ContentAddress(1, 1, recipe.KeySize)
77+
chooserIn(t, screen.Fields(), recipe.TargetAddress(1, recipe.KeyFormat)).SetSelected("zip")
78+
pressNamed(t, body, text.ButtonAddContents())
79+
setBox(t, screen.Fields(), at, "3kb")
80+
81+
// A format that holds nothing. The offer to add more goes, since another
82+
// one would not be legal.
83+
chooserIn(t, screen.Fields(), recipe.TargetAddress(1, recipe.KeyFormat)).SetSelected("txt")
84+
85+
if buttonNamed(body, text.ButtonAddContents()) != nil {
86+
t.Error("a txt batch offers to add files inside it")
87+
}
88+
89+
if findField(screen.Fields(), at) == nil {
90+
t.Fatalf("the row typed into an archive is gone from the screen after the format changed, "+
91+
"so a refusal about %s would have nothing to mark", at)
92+
}
93+
if got := boxText(t, screen.Fields(), at); got != "3kb" {
94+
t.Errorf("the row typed into an archive says %q after the format changed and it was typed as %q",
95+
got, "3kb")
96+
}
97+
if !strings.Contains(everythingSaid(body), text.ContentsHeading()) {
98+
t.Errorf("the rows are still registered and %q is not on the screen, "+
99+
"so what is left is a value nobody can see", text.ContentsHeading())
100+
}
101+
}

‎internal/guard/detailpopup_test.go‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,12 @@ func TestTheLongerExplanationOpensWhenAsked(t *testing.T) {
5353
// nobody can see until they press something, so an unwired button is
5454
// invisible in exactly the way the permanent text was not.
5555
for _, field := range []struct{ label, detail string }{
56-
{text.FieldFormat(), text.DetailFormat()},
56+
// The format field's sentence is the line under its label, which lives
57+
// behind this button like every longer explanation since 2026-08-24.
58+
// It had a second sentence until 2026-08-27 and the button outlived it,
59+
// which is the point: an empty explanation would leave the line with
60+
// nowhere to be read.
61+
{text.FieldFormat(), text.HintFormat()},
5762
{text.FieldSize(), text.DetailSize()},
5863
{text.FieldTargetID(), text.DetailTargetID()},
5964
{text.FieldNameTemplate(), text.DetailNameTemplate()},

‎internal/guard/menudefault_test.go‎

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package guard
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"fyne.io/fyne/v2"
8+
9+
"github.com/donislawdev/TestingFilesGenerator/internal/format"
10+
"github.com/donislawdev/TestingFilesGenerator/internal/gui/parts"
11+
"github.com/donislawdev/TestingFilesGenerator/internal/gui/text"
12+
"github.com/donislawdev/TestingFilesGenerator/internal/gui/window"
13+
"github.com/donislawdev/TestingFilesGenerator/internal/preset"
14+
"github.com/donislawdev/TestingFilesGenerator/internal/recipe"
15+
)
16+
17+
// A menu offers what the declaration lists, and opens on the declared default.
18+
//
19+
// This replaces TestAMenuLeftAloneSaysNothingWasChosen, which held the opposite
20+
// promise: that a menu carries an extra first entry reading "not stated - a4",
21+
// so a default nobody picked could be told from a value somebody chose. The
22+
// reason written on that guard was untouchable rule 5 - the manifest records
23+
// WHICH values were ours - and the reason was never checked against a manifest.
24+
//
25+
// Measured on 2026-08-27, at both ends, and neither end wanted it:
26+
//
27+
// - A format setting. An ICO run leaving embed alone and one asking for
28+
// embed=bmp give the same bytes and the same manifest, which writes
29+
// embed: bmp in both of them. The word defaulted is in neither.
30+
// - A preset setting. Defaulted is built from the parameters a preset
31+
// declares, and the format a preset is built in is a global flag rather
32+
// than one of those, so a run that never states it records
33+
// defaulted: [spread] and never mentions format at all.
34+
//
35+
// So the entry was a third value between two that already did the same thing.
36+
// Removed by the owner's decision. What is guarded instead is the promise that
37+
// replaced it, and it has two halves that fail differently: a menu offering
38+
// something the declaration does not list would let somebody ask for a value
39+
// the engine refuses, and a menu opening on anything but the default would
40+
// silently change what a fresh screen produces.
41+
//
42+
// Boxes somebody types in are a different case and keep the old promise -
43+
// TestABoxYouMayLeaveAloneSaysSo is the guard for those.
44+
func TestAMenuOpensOnItsDeclaredDefault(t *testing.T) {
45+
checked := 0
46+
for _, p := range everyDeclaredMenu() {
47+
p := p
48+
t.Run(p.Name, func(t *testing.T) {
49+
field := parts.FromProperty(p)
50+
menu, ok := field.Control.(*parts.Chooser)
51+
if !ok {
52+
t.Fatalf("%q is a closed set and the window draws %T for it", p.Name, field.Control)
53+
}
54+
checked++
55+
56+
if got, want := strings.Join(menu.Options, ","), strings.Join(p.Choices, ","); got != want {
57+
t.Errorf("the menu for %q offers %s and the declaration lists %s", p.Name, got, want)
58+
}
59+
60+
if p.Default == "" {
61+
// Nothing declared, so nothing chosen. The format works its own
62+
// answer out and the placeholder says so.
63+
if menu.Selected != "" {
64+
t.Errorf("%q declares no default and its menu opens on %q, which is a choice nobody made",
65+
p.Name, menu.Selected)
66+
}
67+
return
68+
}
69+
70+
if menu.Selected != p.Default {
71+
t.Errorf("%q declares the default %q and its menu opens on %q",
72+
p.Name, p.Default, menu.Selected)
73+
}
74+
// Asked of the FIELD rather than read off the control, because the
75+
// value is the half that reaches the engine. A menu showing the
76+
// right word while sending something else onward would be a defect
77+
// wearing a label.
78+
if got := field.Value(); got != p.Default {
79+
t.Errorf("the menu for %q shows %q and sends %q", p.Name, menu.Selected, got)
80+
}
81+
})
82+
}
83+
if checked == 0 {
84+
t.Fatal("no menu was examined - this guard would pass without checking anything")
85+
}
86+
t.Logf("%d menu(s) open on their declared default", checked)
87+
}
88+
89+
// everyDeclaredMenu is every closed set either surface draws, with and without
90+
// a declared default.
91+
//
92+
// Both kinds, deliberately. The old guard looked only at settings that declare
93+
// a default, because the entry it was about only existed for those - which left
94+
// the menus that declare none unwatched by anything asking what they offer.
95+
func everyDeclaredMenu() []format.Property {
96+
var out []format.Property
97+
seen := map[string]bool{}
98+
add := func(p format.Property) {
99+
if p.Kind != format.PropertyChoice || seen[p.Name] {
100+
return
101+
}
102+
seen[p.Name] = true
103+
out = append(out, p)
104+
}
105+
106+
for _, d := range format.All() {
107+
for _, p := range d.Properties {
108+
add(p)
109+
}
110+
}
111+
for _, p := range preset.All() {
112+
for _, param := range append(append([]format.Property{}, p.Parameters...), p.Globals()...) {
113+
add(param)
114+
}
115+
}
116+
return out
117+
}
118+
119+
// Every menu offering the whole list of formats draws the file kind pictures.
120+
//
121+
// There were three such menus on 2026-08-27 and one of them had the pictures:
122+
// the format of a batch. The list inside "Add files inside" and the format a
123+
// preset is built in were drawn plain, so the same twenty values looked like
124+
// different kinds of list depending on which tab somebody was standing on.
125+
// Reported by the owner from the running window.
126+
//
127+
// Asked of the SCREENS rather than of the constructor, and that is the whole
128+
// value of it. The rule now lives in parts.NewChooser, and a guard calling that
129+
// directly would prove the rule and say nothing about whether a screen went
130+
// through it - which is exactly the state this defect was in, since the batch
131+
// screen assigned the pictures by hand and the other two forgot.
132+
func TestEveryMenuOfferingEveryFormatDrawsTheKindPictures(t *testing.T) {
133+
host := newFakeHost(t)
134+
window.Open(host)
135+
136+
look := func(where string, root fyne.CanvasObject) int {
137+
found := 0
138+
walk(root, func(o fyne.CanvasObject) {
139+
menu, ok := o.(*parts.Chooser)
140+
if !ok || !parts.IsEveryFormat(menu.Options) {
141+
return
142+
}
143+
found++
144+
if menu.KindOf == nil {
145+
t.Errorf("a menu of formats on %s draws no pictures, and the one on %s does",
146+
where, text.TabRecipe())
147+
return
148+
}
149+
for _, id := range menu.Options {
150+
if menu.KindOf(id) == nil {
151+
t.Errorf("the menu of formats on %s draws nothing for %q", where, id)
152+
}
153+
}
154+
})
155+
return found
156+
}
157+
158+
// One per screen, counted per screen. A total would let a screen with none
159+
// hide behind a screen with two, which is the shape of the defect itself -
160+
// two menus were missed for twenty days while a third had the pictures.
161+
for _, tab := range []string{text.TabOneTarget(), text.TabRecipe(), text.TabPresets()} {
162+
if n := look(tab, tabNamed(t, host.content, tab)); n != 1 {
163+
t.Errorf("the %s screen has %d menu(s) offering every format and this guard expects 1", tab, n)
164+
}
165+
}
166+
167+
// The fourth is not on any screen until somebody asks for it: the list of
168+
// what an archive holds is drawn by pressing a button, and since 2026-08-27
169+
// that button is only under a format that holds files. Two are expected
170+
// here, since this batch screen still has the batch's own format menu on it.
171+
screen := window.NewRecipe(newFakeHost(t))
172+
body := screen.Object()
173+
chooserIn(t, screen.Fields(), recipe.TargetAddress(1, recipe.KeyFormat)).SetSelected("zip")
174+
pressNamed(t, body, text.ButtonAddContents())
175+
if n := look(text.ContentsHeading(), body); n != 2 {
176+
t.Errorf("a batch holding files has %d menu(s) offering every format and this guard expects 2 - "+
177+
"the batch's own and the one row inside it", n)
178+
}
179+
}

0 commit comments

Comments
 (0)