1
// -- -*- mode: rust -*-
2
//! Tests for fixed behaviors in the WASM frontend
3

            
4
use wasm_bindgen::JsCast;
5
use wasm_bindgen_test::*;
6

            
7
wasm_bindgen_test_configure!(run_in_browser);
8

            
9
/// Test that change events created with `EventInit` bubble up to parent elements.
10
/// This is critical for the currency mismatch detection which uses a document-level
11
/// event listener to catch change events from commodity input fields.
12
#[wasm_bindgen_test]
13
fn test_change_event_bubbles_to_document() {
14
    let window = web_sys::window().unwrap();
15
    let document = window.document().unwrap();
16

            
17
    // Create a container and input
18
    let container = document.create_element("div").unwrap();
19
    let input = document
20
        .create_element("input")
21
        .unwrap()
22
        .dyn_into::<web_sys::HtmlInputElement>()
23
        .unwrap();
24
    container.append_child(&input).unwrap();
25
    document.body().unwrap().append_child(&container).unwrap();
26

            
27
    // Track if the event bubbled to the container
28
    let bubbled = std::rc::Rc::new(std::cell::Cell::new(false));
29
    let bubbled_clone = bubbled.clone();
30

            
31
    let callback = wasm_bindgen::closure::Closure::wrap(Box::new(move |_: web_sys::Event| {
32
        bubbled_clone.set(true);
33
    }) as Box<dyn FnMut(_)>);
34

            
35
    container
36
        .add_event_listener_with_callback("change", callback.as_ref().unchecked_ref())
37
        .unwrap();
38
    callback.forget();
39

            
40
    // Create a bubbling change event (like our autocomplete does)
41
    let init = web_sys::EventInit::new();
42
    init.set_bubbles(true);
43
    let event = web_sys::Event::new_with_event_init_dict("change", &init).unwrap();
44
    input.dispatch_event(&event).unwrap();
45

            
46
    assert!(
47
        bubbled.get(),
48
        "Change event should bubble to parent container"
49
    );
50

            
51
    // Cleanup
52
    container.remove();
53
}
54

            
55
/// Test that non-bubbling events do NOT reach parent elements.
56
/// This demonstrates why we needed to add bubbles: true.
57
#[wasm_bindgen_test]
58
fn test_non_bubbling_event_stays_local() {
59
    let window = web_sys::window().unwrap();
60
    let document = window.document().unwrap();
61

            
62
    let container = document.create_element("div").unwrap();
63
    let input = document
64
        .create_element("input")
65
        .unwrap()
66
        .dyn_into::<web_sys::HtmlInputElement>()
67
        .unwrap();
68
    container.append_child(&input).unwrap();
69
    document.body().unwrap().append_child(&container).unwrap();
70

            
71
    let bubbled = std::rc::Rc::new(std::cell::Cell::new(false));
72
    let bubbled_clone = bubbled.clone();
73

            
74
    let callback = wasm_bindgen::closure::Closure::wrap(Box::new(move |_: web_sys::Event| {
75
        bubbled_clone.set(true);
76
    }) as Box<dyn FnMut(_)>);
77

            
78
    container
79
        .add_event_listener_with_callback("change", callback.as_ref().unchecked_ref())
80
        .unwrap();
81
    callback.forget();
82

            
83
    // Create a NON-bubbling event (default behavior without EventInit)
84
    let event = web_sys::Event::new("change").unwrap();
85
    input.dispatch_event(&event).unwrap();
86

            
87
    assert!(
88
        !bubbled.get(),
89
        "Non-bubbling event should NOT reach parent container"
90
    );
91

            
92
    container.remove();
93
}
94

            
95
/// Test that mousedown with preventDefault stops blur from happening.
96
/// This is critical for mobile Safari autocomplete selection.
97
#[wasm_bindgen_test]
98
fn test_mousedown_prevent_default_stops_blur() {
99
    let window = web_sys::window().unwrap();
100
    let document = window.document().unwrap();
101

            
102
    let input = document
103
        .create_element("input")
104
        .unwrap()
105
        .dyn_into::<web_sys::HtmlInputElement>()
106
        .unwrap();
107
    let dropdown = document.create_element("div").unwrap();
108

            
109
    document.body().unwrap().append_child(&input).unwrap();
110
    document.body().unwrap().append_child(&dropdown).unwrap();
111

            
112
    // Add mousedown handler that prevents default (like our autocomplete)
113
    let mousedown_callback =
114
        wasm_bindgen::closure::Closure::wrap(Box::new(move |e: web_sys::MouseEvent| {
115
            e.prevent_default();
116
        }) as Box<dyn FnMut(_)>);
117

            
118
    dropdown
119
        .add_event_listener_with_callback("mousedown", mousedown_callback.as_ref().unchecked_ref())
120
        .unwrap();
121
    mousedown_callback.forget();
122

            
123
    // Focus the input
124
    input.focus().unwrap();
125

            
126
    // The mousedown with preventDefault should not cause blur
127
    // (In a real browser, clicking the dropdown would blur the input without this)
128
    let init = web_sys::MouseEventInit::new();
129
    init.set_bubbles(true);
130
    init.set_cancelable(true);
131
    let mousedown =
132
        web_sys::MouseEvent::new_with_mouse_event_init_dict("mousedown", &init).unwrap();
133
    let default_prevented = !dropdown.dispatch_event(&mousedown).unwrap();
134

            
135
    assert!(
136
        default_prevented,
137
        "mousedown preventDefault should be called"
138
    );
139

            
140
    input.remove();
141
    dropdown.remove();
142
}
143

            
144
/// Test that detecting existing split entries works correctly.
145
/// The edit page should not fetch new splits if splits already exist.
146
#[wasm_bindgen_test]
147
fn test_split_entry_detection() {
148
    let window = web_sys::window().unwrap();
149
    let document = window.document().unwrap();
150

            
151
    // Create splits container WITHOUT any split entries (create page scenario)
152
    let container = document.create_element("div").unwrap();
153
    container.set_id("splits-container");
154
    document.body().unwrap().append_child(&container).unwrap();
155

            
156
    // Should NOT find any split entries
157
    let has_splits = container
158
        .query_selector(".split-entry")
159
        .ok()
160
        .flatten()
161
        .is_some();
162
    assert!(!has_splits, "Empty container should have no split entries");
163

            
164
    // Now add a split entry (edit page scenario)
165
    let split = document.create_element("div").unwrap();
166
    split.set_class_name("split-entry");
167
    container.append_child(&split).unwrap();
168

            
169
    // Should find the split entry
170
    let has_splits = container
171
        .query_selector(".split-entry")
172
        .ok()
173
        .flatten()
174
        .is_some();
175
    assert!(has_splits, "Container with split-entry should be detected");
176

            
177
    container.remove();
178
}
179

            
180
/// Test that input events also bubble (used for autocomplete filtering).
181
#[wasm_bindgen_test]
182
fn test_input_event_bubbles() {
183
    let window = web_sys::window().unwrap();
184
    let document = window.document().unwrap();
185

            
186
    let container = document.create_element("div").unwrap();
187
    let input = document
188
        .create_element("input")
189
        .unwrap()
190
        .dyn_into::<web_sys::HtmlInputElement>()
191
        .unwrap();
192
    container.append_child(&input).unwrap();
193
    document.body().unwrap().append_child(&container).unwrap();
194

            
195
    let bubbled = std::rc::Rc::new(std::cell::Cell::new(false));
196
    let bubbled_clone = bubbled.clone();
197

            
198
    let callback = wasm_bindgen::closure::Closure::wrap(Box::new(move |_: web_sys::Event| {
199
        bubbled_clone.set(true);
200
    }) as Box<dyn FnMut(_)>);
201

            
202
    container
203
        .add_event_listener_with_callback("input", callback.as_ref().unchecked_ref())
204
        .unwrap();
205
    callback.forget();
206

            
207
    let init = web_sys::EventInit::new();
208
    init.set_bubbles(true);
209
    let event = web_sys::Event::new_with_event_init_dict("input", &init).unwrap();
210
    input.dispatch_event(&event).unwrap();
211

            
212
    assert!(bubbled.get(), "Input event should bubble to parent");
213

            
214
    container.remove();
215
}