@reventlessdev/trait-attachments 1.0.0-alpha.1 → 1.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/Attachments.res +83 -8
- package/src/Attachments_Conformance.res +131 -12
- package/src/Attachments_Conformance.res.mjs +79 -10
- package/src/Attachments_Rules.res +167 -17
- package/src/Attachments_Rules.res.mjs +113 -36
- package/src/Attachments_Scaffold.res +495 -193
- package/src/Attachments_Scaffold.res.mjs +390 -156
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/trait-attachments",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.10",
|
|
4
4
|
"description": "Attachments domain trait: rules, host contract, conformance suite and emitter for an ordered set of stored files on an entity, with a primary and captions",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"files": [
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"sury": "11.0.0-rc.2",
|
|
15
15
|
"sury-ppx": "11.0.0-rc.2",
|
|
16
|
-
"@reventlessdev/reventless-gwt": "1.0.0-alpha.
|
|
17
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
16
|
+
"@reventlessdev/reventless-gwt": "1.0.0-alpha.208",
|
|
17
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.134"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"rescript": "12.3.0"
|
package/src/Attachments.res
CHANGED
|
@@ -6,7 +6,6 @@ contract is over that slice. The rules live in `Attachments_Rules` and are asser
|
|
|
6
6
|
through a host by `Attachments_Conformance`; the spec surface the host maps onto
|
|
7
7
|
them is written by `Attachments_Scaffold`.
|
|
8
8
|
*/
|
|
9
|
-
|
|
10
9
|
/**
|
|
11
10
|
This trait's own account of itself — see `AddressGeocoding.declaration` for why the
|
|
12
11
|
version is read rather than written.
|
|
@@ -43,13 +42,27 @@ module type Binding = {
|
|
|
43
42
|
module Spec: ReventlessGwt.Behavior_GWT.BehaviorSpec
|
|
44
43
|
module Behavior: ReventlessGwt.Behavior_GWT.Behavior with module Spec = Spec
|
|
45
44
|
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
/** The same facts as the slice **consumes** them, for building history.
|
|
46
|
+
|
|
47
|
+
A slice declares `consumedEvent` and `event` separately and they differ —
|
|
48
|
+
the consumed form carries no entity id, because the partition already
|
|
49
|
+
says which entity it belongs to. So a suite needs both spellings of the
|
|
50
|
+
same fact: one to lay down history, one to assert what was emitted.
|
|
51
|
+
|
|
52
|
+
Grouped rather than suffixed. The pairs used to read `attachedC` against
|
|
53
|
+
`attached`, which said "consumed" only to someone who already knew — and
|
|
54
|
+
in a codebase where every binding also carries commands, a bare `C` reads
|
|
55
|
+
just as naturally as one. */
|
|
56
|
+
module Consumed: {
|
|
57
|
+
/** History that brings the entity into existence with an empty set. */
|
|
58
|
+
let created: array<Spec.consumedEvent>
|
|
59
|
+
let attached: ref => Spec.consumedEvent
|
|
60
|
+
let removed: ref => Spec.consumedEvent
|
|
61
|
+
let primarySet: ref => Spec.consumedEvent
|
|
62
|
+
/** The conclusion — see `Attachments_Rules.fact`. */
|
|
63
|
+
let effectiveChanged: option<ref> => Spec.consumedEvent
|
|
64
|
+
let altTextSet: (ref, string) => Spec.consumedEvent
|
|
65
|
+
}
|
|
53
66
|
|
|
54
67
|
let attach: ref => Spec.command
|
|
55
68
|
let remove: ref => Spec.command
|
|
@@ -60,7 +73,69 @@ module type Binding = {
|
|
|
60
73
|
let attached: ref => Spec.event
|
|
61
74
|
let removed: ref => Spec.event
|
|
62
75
|
let primarySet: ref => Spec.event
|
|
76
|
+
/** The member a reader should now show, or `None` where the set has none.
|
|
77
|
+
Every graft declares it, whatever its cardinality: a subscriber reading it
|
|
78
|
+
must not have to know how many members the host allows. */
|
|
79
|
+
let effectiveChanged: option<ref> => Spec.event
|
|
63
80
|
let altTextSet: (ref, string) => Spec.event
|
|
64
81
|
/** The refusal for a primary or caption on a ref that is not in the set. */
|
|
65
82
|
let notAttached: Spec.error
|
|
66
83
|
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
One host of the **bounded** cardinality, bound.
|
|
87
|
+
|
|
88
|
+
A separate contract rather than optional members on {!Binding}, because what
|
|
89
|
+
`Attachments_Rules.Single` changes is the host's command surface and not just a
|
|
90
|
+
rule: there is no primary to choose between one member, so the graft declares no
|
|
91
|
+
such command, and there is no ref for a remove to name, so it declares none.
|
|
92
|
+
Both absences are facts a module type states better than a test does — a
|
|
93
|
+
`Single` graft that grew a `SetPrimary` command would fail to satisfy this
|
|
94
|
+
signature, which is the check, and it happens at compile time.
|
|
95
|
+
|
|
96
|
+
Everything the two contracts share is spelled identically on purpose: a reader
|
|
97
|
+
comparing them should find the difference in what is missing, not in how the
|
|
98
|
+
common half is phrased.
|
|
99
|
+
*/
|
|
100
|
+
module type SingleBinding = {
|
|
101
|
+
type ref
|
|
102
|
+
/** Two refs that differ — the second is what replaces the first. */
|
|
103
|
+
let refA: ref
|
|
104
|
+
let refB: ref
|
|
105
|
+
|
|
106
|
+
module Spec: ReventlessGwt.Behavior_GWT.BehaviorSpec
|
|
107
|
+
module Behavior: ReventlessGwt.Behavior_GWT.Behavior with module Spec = Spec
|
|
108
|
+
|
|
109
|
+
/** The facts as the slice **consumes** them — see `Binding.Consumed`. No
|
|
110
|
+
`primarySet`: at this cardinality nothing emits one. */
|
|
111
|
+
module Consumed: {
|
|
112
|
+
/** History that brings the entity into existence with an empty set. */
|
|
113
|
+
let created: array<Spec.consumedEvent>
|
|
114
|
+
let attached: ref => Spec.consumedEvent
|
|
115
|
+
let removed: ref => Spec.consumedEvent
|
|
116
|
+
let altTextSet: (ref, string) => Spec.consumedEvent
|
|
117
|
+
/** Declared at this cardinality too even though `attached`/`removed` imply
|
|
118
|
+
it here: a subscriber must not have to know how many members a host
|
|
119
|
+
allows to know what to read. */
|
|
120
|
+
let effectiveChanged: option<ref> => Spec.consumedEvent
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let attach: ref => Spec.command
|
|
124
|
+
/** Remove whatever is held. Takes no ref — there is only one, and asking the
|
|
125
|
+
caller to name it is asking them to repeat what the row already says. */
|
|
126
|
+
let clear: Spec.command
|
|
127
|
+
/** Caption whatever is held. Ref-less for `clear`'s reason, and there is a
|
|
128
|
+
second one here: a bounded host's view carries the reference as a scalar
|
|
129
|
+
and no collection at all, so there is no field for a selection to be a
|
|
130
|
+
member *of*. */
|
|
131
|
+
let setAltText: string => Spec.command
|
|
132
|
+
|
|
133
|
+
/** The set's facts, as the slice emits them. */
|
|
134
|
+
let attached: ref => Spec.event
|
|
135
|
+
let removed: ref => Spec.event
|
|
136
|
+
let altTextSet: (ref, string) => Spec.event
|
|
137
|
+
/** The member a reader should now show, or `None` where nothing is held. */
|
|
138
|
+
let effectiveChanged: option<ref> => Spec.event
|
|
139
|
+
/** The refusal for a caption on a ref that is not held. */
|
|
140
|
+
let notAttached: Spec.error
|
|
141
|
+
}
|
|
@@ -2,23 +2,29 @@
|
|
|
2
2
|
The conformance suite, run by a host against its own graft. `Make(Binding).register()`
|
|
3
3
|
inside a Jest test file registers one `describe` block over the binding.
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
5
|
/** The suite's title, composed once and read twice: the suite registers it,
|
|
7
6
|
and `certify-trait` computes the same string to find the run's assertions in
|
|
8
7
|
a test report. Exported rather than inlined so neither side parses the
|
|
9
|
-
other's prose.
|
|
8
|
+
other's prose.
|
|
9
|
+
|
|
10
|
+
One title for both cardinalities: a host conforms to this trait or it does
|
|
11
|
+
not, and which of the two suites established that is not a fact a
|
|
12
|
+
certificate should have to know. `MakeSingle` registers under the same
|
|
13
|
+
name. */
|
|
10
14
|
let suiteName = (host: string) => `${host} conforms to the attachments trait`
|
|
11
15
|
|
|
12
16
|
module Make = (B: Attachments.Binding) => {
|
|
13
17
|
module G = ReventlessGwt.Behavior_GWT.Make(B.Spec, B.Behavior)
|
|
14
18
|
|
|
15
|
-
let withA = Array.concat(B.created, [B.
|
|
16
|
-
let withAB = Array.concat(withA, [B.
|
|
19
|
+
let withA = Array.concat(B.Consumed.created, [B.Consumed.attached(B.refA)])
|
|
20
|
+
let withAB = Array.concat(withA, [B.Consumed.attached(B.refB)])
|
|
17
21
|
|
|
18
22
|
let register = () =>
|
|
19
23
|
G.describe(suiteName(B.Spec.name), () => {
|
|
20
24
|
G.test("the first attachment is appended", () =>
|
|
21
|
-
G.givenEvents(B.created)
|
|
25
|
+
G.givenEvents(B.Consumed.created)
|
|
26
|
+
->G.whenCmd(B.attach(B.refA))
|
|
27
|
+
->G.thenEvents([B.attached(B.refA), B.effectiveChanged(Some(B.refA))])
|
|
22
28
|
)
|
|
23
29
|
|
|
24
30
|
G.test("attaching a ref already in the set is a no-op", () =>
|
|
@@ -30,7 +36,9 @@ module Make = (B: Attachments.Binding) => {
|
|
|
30
36
|
)
|
|
31
37
|
|
|
32
38
|
G.test("removing an attached ref is appended", () =>
|
|
33
|
-
G.givenEvents(withA)
|
|
39
|
+
G.givenEvents(withA)
|
|
40
|
+
->G.whenCmd(B.remove(B.refA))
|
|
41
|
+
->G.thenEvents([B.removed(B.refA), B.effectiveChanged(None)])
|
|
34
42
|
)
|
|
35
43
|
|
|
36
44
|
G.test("removing a ref not in the set is a no-op", () =>
|
|
@@ -38,9 +46,9 @@ module Make = (B: Attachments.Binding) => {
|
|
|
38
46
|
)
|
|
39
47
|
|
|
40
48
|
G.test("a removed ref can be attached again", () =>
|
|
41
|
-
G.givenEvents(Array.concat(withA, [B.
|
|
49
|
+
G.givenEvents(Array.concat(withA, [B.Consumed.removed(B.refA)]))
|
|
42
50
|
->G.whenCmd(B.attach(B.refA))
|
|
43
|
-
->G.
|
|
51
|
+
->G.thenEvents([B.attached(B.refA), B.effectiveChanged(Some(B.refA))])
|
|
44
52
|
)
|
|
45
53
|
|
|
46
54
|
G.test("the primary must be in the set", () =>
|
|
@@ -52,27 +60,50 @@ module Make = (B: Attachments.Binding) => {
|
|
|
52
60
|
)
|
|
53
61
|
|
|
54
62
|
G.test("choosing another primary is appended", () =>
|
|
55
|
-
G.givenEvents(withAB)
|
|
63
|
+
G.givenEvents(withAB)
|
|
64
|
+
->G.whenCmd(B.setPrimary(B.refB))
|
|
65
|
+
->G.thenEvents([B.primarySet(B.refB), B.effectiveChanged(Some(B.refB))])
|
|
56
66
|
)
|
|
57
67
|
|
|
58
68
|
G.test("choosing the current primary is a no-op", () =>
|
|
59
|
-
G.givenEvents(Array.concat(withAB, [B.
|
|
69
|
+
G.givenEvents(Array.concat(withAB, [B.Consumed.primarySet(B.refB)]))
|
|
60
70
|
->G.whenCmd(B.setPrimary(B.refB))
|
|
61
71
|
->G.thenNoEvent
|
|
62
72
|
)
|
|
63
73
|
|
|
64
74
|
G.test("removing the chosen primary lets the first remaining stand in", () =>
|
|
65
|
-
G.givenEvents(
|
|
75
|
+
G.givenEvents(
|
|
76
|
+
Array.concat(withAB, [B.Consumed.primarySet(B.refB), B.Consumed.removed(B.refB)]),
|
|
77
|
+
)
|
|
66
78
|
->G.whenCmd(B.setPrimary(B.refA))
|
|
67
79
|
->G.thenNoEvent
|
|
68
80
|
)
|
|
69
81
|
|
|
82
|
+
// The two moves nobody decides. Neither produces a fact of its own — the
|
|
83
|
+
// set simply resolves differently afterwards — which is why anything
|
|
84
|
+
// outside it could not follow the primary before this was announced.
|
|
85
|
+
G.test("removing the member that stood in promotes the next, and says so", () =>
|
|
86
|
+
G.givenEvents(withAB)
|
|
87
|
+
->G.whenCmd(B.remove(B.refA))
|
|
88
|
+
->G.thenEvents([B.removed(B.refA), B.effectiveChanged(Some(B.refB))])
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
G.test("removing the chosen primary announces the one that stands in", () =>
|
|
92
|
+
G.givenEvents(Array.concat(withAB, [B.Consumed.primarySet(B.refB)]))
|
|
93
|
+
->G.whenCmd(B.remove(B.refB))
|
|
94
|
+
->G.thenEvents([B.removed(B.refB), B.effectiveChanged(Some(B.refA))])
|
|
95
|
+
)
|
|
96
|
+
|
|
70
97
|
G.test("a caption needs its ref in the set", () =>
|
|
71
98
|
G.givenEvents(withA)
|
|
72
99
|
->G.whenCmd(B.setAltText(B.refB, "side"))
|
|
73
100
|
->G.thenError(B.notAttached)
|
|
74
101
|
)
|
|
75
102
|
|
|
103
|
+
// One fact and no second one: a caption moves no file, so the conclusion
|
|
104
|
+
// is unchanged and must not be announced. This is what stops an
|
|
105
|
+
// implementation appending it after every command rather than after the
|
|
106
|
+
// ones that move it.
|
|
76
107
|
G.test("a caption is appended", () =>
|
|
77
108
|
G.givenEvents(withA)
|
|
78
109
|
->G.whenCmd(B.setAltText(B.refA, "front"))
|
|
@@ -80,9 +111,97 @@ module Make = (B: Attachments.Binding) => {
|
|
|
80
111
|
)
|
|
81
112
|
|
|
82
113
|
G.test("repeating the caption is a no-op", () =>
|
|
83
|
-
G.givenEvents(Array.concat(withA, [B.
|
|
114
|
+
G.givenEvents(Array.concat(withA, [B.Consumed.altTextSet(B.refA, "front")]))
|
|
84
115
|
->G.whenCmd(B.setAltText(B.refA, "front"))
|
|
85
116
|
->G.thenNoEvent
|
|
86
117
|
)
|
|
87
118
|
})
|
|
88
119
|
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
The suite for a host of the bounded cardinality.
|
|
123
|
+
|
|
124
|
+
Smaller than `Make`'s, and smaller for a reason worth reading: most of what the
|
|
125
|
+
larger suite asserts is about choosing between members, and a set that holds one
|
|
126
|
+
member has nothing to choose. Those assertions are not skipped here — they are
|
|
127
|
+
unreachable, because {!Attachments.SingleBinding} does not admit the commands
|
|
128
|
+
that would reach them. The absent primary is checked by the compiler.
|
|
129
|
+
|
|
130
|
+
What is left is the one rule cardinality actually changes — a second attachment
|
|
131
|
+
replaces rather than joins — plus the ref-less remove and the captions, which a
|
|
132
|
+
single image wants exactly as much as a gallery member does.
|
|
133
|
+
*/
|
|
134
|
+
module MakeSingle = (B: Attachments.SingleBinding) => {
|
|
135
|
+
module G = ReventlessGwt.Behavior_GWT.Make(B.Spec, B.Behavior)
|
|
136
|
+
|
|
137
|
+
let withA = Array.concat(B.Consumed.created, [B.Consumed.attached(B.refA)])
|
|
138
|
+
|
|
139
|
+
let register = () =>
|
|
140
|
+
G.describe(suiteName(B.Spec.name), () => {
|
|
141
|
+
G.test("the first attachment is appended", () =>
|
|
142
|
+
G.givenEvents(B.Consumed.created)
|
|
143
|
+
->G.whenCmd(B.attach(B.refA))
|
|
144
|
+
->G.thenEvents([B.attached(B.refA), B.effectiveChanged(Some(B.refA))])
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
G.test("attaching the ref already held is a no-op", () =>
|
|
148
|
+
G.givenEvents(withA)->G.whenCmd(B.attach(B.refA))->G.thenNoEvent
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
// The assertion the cardinality exists for. Two facts, in order: what was
|
|
152
|
+
// there leaves before what replaces it arrives, so a reader replaying the
|
|
153
|
+
// log never sees the set hold both.
|
|
154
|
+
G.test("a second ref replaces the first rather than joining it", () =>
|
|
155
|
+
G.givenEvents(withA)
|
|
156
|
+
->G.whenCmd(B.attach(B.refB))
|
|
157
|
+
->G.thenEvents([B.removed(B.refA), B.attached(B.refB), B.effectiveChanged(Some(B.refB))])
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
G.test("clearing a held set removes what it holds", () =>
|
|
161
|
+
G.givenEvents(withA)
|
|
162
|
+
->G.whenCmd(B.clear)
|
|
163
|
+
->G.thenEvents([B.removed(B.refA), B.effectiveChanged(None)])
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
G.test("clearing an empty set is a no-op", () =>
|
|
167
|
+
G.givenEvents(B.Consumed.created)->G.whenCmd(B.clear)->G.thenNoEvent
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
G.test("a replaced ref can be attached again", () =>
|
|
171
|
+
G.givenEvents(
|
|
172
|
+
Array.concat(withA, [B.Consumed.removed(B.refA), B.Consumed.attached(B.refB)]),
|
|
173
|
+
)
|
|
174
|
+
->G.whenCmd(B.attach(B.refA))
|
|
175
|
+
->G.thenEvents([B.removed(B.refB), B.attached(B.refA), B.effectiveChanged(Some(B.refA))])
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
// The caption names no ref either, so what it lands on is whatever is
|
|
179
|
+
// held — and the assertion is that it lands on the *right* ref, which is
|
|
180
|
+
// the only thing a ref-less command can get wrong.
|
|
181
|
+
G.test("a caption lands on the ref that is held", () =>
|
|
182
|
+
G.givenEvents(withA)
|
|
183
|
+
->G.whenCmd(B.setAltText("front"))
|
|
184
|
+
->G.thenEvent(B.altTextSet(B.refA, "front"))
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
G.test("a caption follows a replacement onto the new ref", () =>
|
|
188
|
+
G.givenEvents(
|
|
189
|
+
Array.concat(withA, [B.Consumed.removed(B.refA), B.Consumed.attached(B.refB)]),
|
|
190
|
+
)
|
|
191
|
+
->G.whenCmd(B.setAltText("side"))
|
|
192
|
+
->G.thenEvent(B.altTextSet(B.refB, "side"))
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
G.test("captioning an empty set is refused", () =>
|
|
196
|
+
G.givenEvents(B.Consumed.created)
|
|
197
|
+
->G.whenCmd(B.setAltText("front"))
|
|
198
|
+
->G.thenError(B.notAttached)
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
G.test("repeating the caption is a no-op", () =>
|
|
202
|
+
G.givenEvents(Array.concat(withA, [B.Consumed.altTextSet(B.refA, "front")]))
|
|
203
|
+
->G.whenCmd(B.setAltText("front"))
|
|
204
|
+
->G.thenNoEvent
|
|
205
|
+
)
|
|
206
|
+
})
|
|
207
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
3
4
|
import * as Behavior_GWT$ReventlessGwt from "@reventlessdev/reventless-gwt/src/Behavior_GWT.res.mjs";
|
|
4
5
|
|
|
5
6
|
function suiteName(host) {
|
|
@@ -13,26 +14,46 @@ function Make(B) {
|
|
|
13
14
|
evolve: $$let.evolve,
|
|
14
15
|
decide: $$let.decide
|
|
15
16
|
});
|
|
16
|
-
let withA = B.created.concat([B.
|
|
17
|
-
let withAB = withA.concat([B.
|
|
17
|
+
let withA = B.Consumed.created.concat([B.Consumed.attached(B.refA)]);
|
|
18
|
+
let withAB = withA.concat([B.Consumed.attached(B.refB)]);
|
|
18
19
|
let register = () => G.describe(suiteName(B.Spec.name), () => {
|
|
19
|
-
G.test("the first attachment is appended", () => G.
|
|
20
|
+
G.test("the first attachment is appended", () => G.thenEvents(G.whenCmd(G.givenEvents(B.Consumed.created), B.attach(B.refA)), [
|
|
21
|
+
B.attached(B.refA),
|
|
22
|
+
B.effectiveChanged(Primitive_option.some(B.refA))
|
|
23
|
+
]));
|
|
20
24
|
G.test("attaching a ref already in the set is a no-op", () => G.thenNoEvent(G.whenCmd(G.givenEvents(withA), B.attach(B.refA))));
|
|
21
25
|
G.test("a second ref extends the set rather than replacing it", () => G.thenEvent(G.whenCmd(G.givenEvents(withA), B.attach(B.refB)), B.attached(B.refB)));
|
|
22
|
-
G.test("removing an attached ref is appended", () => G.
|
|
26
|
+
G.test("removing an attached ref is appended", () => G.thenEvents(G.whenCmd(G.givenEvents(withA), B.remove(B.refA)), [
|
|
27
|
+
B.removed(B.refA),
|
|
28
|
+
B.effectiveChanged(undefined)
|
|
29
|
+
]));
|
|
23
30
|
G.test("removing a ref not in the set is a no-op", () => G.thenNoEvent(G.whenCmd(G.givenEvents(withA), B.remove(B.refB))));
|
|
24
|
-
G.test("a removed ref can be attached again", () => G.
|
|
31
|
+
G.test("a removed ref can be attached again", () => G.thenEvents(G.whenCmd(G.givenEvents(withA.concat([B.Consumed.removed(B.refA)])), B.attach(B.refA)), [
|
|
32
|
+
B.attached(B.refA),
|
|
33
|
+
B.effectiveChanged(Primitive_option.some(B.refA))
|
|
34
|
+
]));
|
|
25
35
|
G.test("the primary must be in the set", () => G.thenError(G.whenCmd(G.givenEvents(withA), B.setPrimary(B.refB)), B.notAttached));
|
|
26
36
|
G.test("the first attached is the primary until one is chosen", () => G.thenNoEvent(G.whenCmd(G.givenEvents(withAB), B.setPrimary(B.refA))));
|
|
27
|
-
G.test("choosing another primary is appended", () => G.
|
|
28
|
-
|
|
37
|
+
G.test("choosing another primary is appended", () => G.thenEvents(G.whenCmd(G.givenEvents(withAB), B.setPrimary(B.refB)), [
|
|
38
|
+
B.primarySet(B.refB),
|
|
39
|
+
B.effectiveChanged(Primitive_option.some(B.refB))
|
|
40
|
+
]));
|
|
41
|
+
G.test("choosing the current primary is a no-op", () => G.thenNoEvent(G.whenCmd(G.givenEvents(withAB.concat([B.Consumed.primarySet(B.refB)])), B.setPrimary(B.refB))));
|
|
29
42
|
G.test("removing the chosen primary lets the first remaining stand in", () => G.thenNoEvent(G.whenCmd(G.givenEvents(withAB.concat([
|
|
30
|
-
B.
|
|
31
|
-
B.
|
|
43
|
+
B.Consumed.primarySet(B.refB),
|
|
44
|
+
B.Consumed.removed(B.refB)
|
|
32
45
|
])), B.setPrimary(B.refA))));
|
|
46
|
+
G.test("removing the member that stood in promotes the next, and says so", () => G.thenEvents(G.whenCmd(G.givenEvents(withAB), B.remove(B.refA)), [
|
|
47
|
+
B.removed(B.refA),
|
|
48
|
+
B.effectiveChanged(Primitive_option.some(B.refB))
|
|
49
|
+
]));
|
|
50
|
+
G.test("removing the chosen primary announces the one that stands in", () => G.thenEvents(G.whenCmd(G.givenEvents(withAB.concat([B.Consumed.primarySet(B.refB)])), B.remove(B.refB)), [
|
|
51
|
+
B.removed(B.refB),
|
|
52
|
+
B.effectiveChanged(Primitive_option.some(B.refA))
|
|
53
|
+
]));
|
|
33
54
|
G.test("a caption needs its ref in the set", () => G.thenError(G.whenCmd(G.givenEvents(withA), B.setAltText(B.refB, "side")), B.notAttached));
|
|
34
55
|
G.test("a caption is appended", () => G.thenEvent(G.whenCmd(G.givenEvents(withA), B.setAltText(B.refA, "front")), B.altTextSet(B.refA, "front")));
|
|
35
|
-
G.test("repeating the caption is a no-op", () => G.thenNoEvent(G.whenCmd(G.givenEvents(withA.concat([B.
|
|
56
|
+
G.test("repeating the caption is a no-op", () => G.thenNoEvent(G.whenCmd(G.givenEvents(withA.concat([B.Consumed.altTextSet(B.refA, "front")])), B.setAltText(B.refA, "front"))));
|
|
36
57
|
});
|
|
37
58
|
return {
|
|
38
59
|
G: G,
|
|
@@ -42,8 +63,56 @@ function Make(B) {
|
|
|
42
63
|
};
|
|
43
64
|
}
|
|
44
65
|
|
|
66
|
+
function MakeSingle(B) {
|
|
67
|
+
let $$let = B.Behavior;
|
|
68
|
+
let G = Behavior_GWT$ReventlessGwt.Make(B.Spec)({
|
|
69
|
+
initialState: $$let.initialState,
|
|
70
|
+
evolve: $$let.evolve,
|
|
71
|
+
decide: $$let.decide
|
|
72
|
+
});
|
|
73
|
+
let withA = B.Consumed.created.concat([B.Consumed.attached(B.refA)]);
|
|
74
|
+
let register = () => G.describe(suiteName(B.Spec.name), () => {
|
|
75
|
+
G.test("the first attachment is appended", () => G.thenEvents(G.whenCmd(G.givenEvents(B.Consumed.created), B.attach(B.refA)), [
|
|
76
|
+
B.attached(B.refA),
|
|
77
|
+
B.effectiveChanged(Primitive_option.some(B.refA))
|
|
78
|
+
]));
|
|
79
|
+
G.test("attaching the ref already held is a no-op", () => G.thenNoEvent(G.whenCmd(G.givenEvents(withA), B.attach(B.refA))));
|
|
80
|
+
G.test("a second ref replaces the first rather than joining it", () => G.thenEvents(G.whenCmd(G.givenEvents(withA), B.attach(B.refB)), [
|
|
81
|
+
B.removed(B.refA),
|
|
82
|
+
B.attached(B.refB),
|
|
83
|
+
B.effectiveChanged(Primitive_option.some(B.refB))
|
|
84
|
+
]));
|
|
85
|
+
G.test("clearing a held set removes what it holds", () => G.thenEvents(G.whenCmd(G.givenEvents(withA), B.clear), [
|
|
86
|
+
B.removed(B.refA),
|
|
87
|
+
B.effectiveChanged(undefined)
|
|
88
|
+
]));
|
|
89
|
+
G.test("clearing an empty set is a no-op", () => G.thenNoEvent(G.whenCmd(G.givenEvents(B.Consumed.created), B.clear)));
|
|
90
|
+
G.test("a replaced ref can be attached again", () => G.thenEvents(G.whenCmd(G.givenEvents(withA.concat([
|
|
91
|
+
B.Consumed.removed(B.refA),
|
|
92
|
+
B.Consumed.attached(B.refB)
|
|
93
|
+
])), B.attach(B.refA)), [
|
|
94
|
+
B.removed(B.refB),
|
|
95
|
+
B.attached(B.refA),
|
|
96
|
+
B.effectiveChanged(Primitive_option.some(B.refA))
|
|
97
|
+
]));
|
|
98
|
+
G.test("a caption lands on the ref that is held", () => G.thenEvent(G.whenCmd(G.givenEvents(withA), B.setAltText("front")), B.altTextSet(B.refA, "front")));
|
|
99
|
+
G.test("a caption follows a replacement onto the new ref", () => G.thenEvent(G.whenCmd(G.givenEvents(withA.concat([
|
|
100
|
+
B.Consumed.removed(B.refA),
|
|
101
|
+
B.Consumed.attached(B.refB)
|
|
102
|
+
])), B.setAltText("side")), B.altTextSet(B.refB, "side")));
|
|
103
|
+
G.test("captioning an empty set is refused", () => G.thenError(G.whenCmd(G.givenEvents(B.Consumed.created), B.setAltText("front")), B.notAttached));
|
|
104
|
+
G.test("repeating the caption is a no-op", () => G.thenNoEvent(G.whenCmd(G.givenEvents(withA.concat([B.Consumed.altTextSet(B.refA, "front")])), B.setAltText("front"))));
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
G: G,
|
|
108
|
+
withA: withA,
|
|
109
|
+
register: register
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
45
113
|
export {
|
|
46
114
|
suiteName,
|
|
47
115
|
Make,
|
|
116
|
+
MakeSingle,
|
|
48
117
|
}
|
|
49
118
|
/* Behavior_GWT-ReventlessGwt Not a pure module */
|