use super::*;
#[test]
fn timer_interval_supports_multiple_additional_parameters() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
let id = 0;
document.getElementById('btn').addEventListener('click', () => {
let tick = 0;
id = setInterval((value, suffix) => {
tick = tick + 1;
document.getElementById('result').textContent =
document.getElementById('result').textContent + value + suffix;
if (tick > 2) {
clearInterval(id);
}
}, 0, 'I', '!');
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.flush()?;
h.assert_text("#result", "I!I!I!")?;
Ok(())
}
#[test]
fn timer_interval_supports_string_code_callback() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
setInterval(
"document.getElementById('result').textContent = document.getElementById('result').textContent + 'X';",
5
);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "")?;
h.advance_time(5)?;
h.assert_text("#result", "X")?;
assert!(h.clear_timer(1));
Ok(())
}
#[test]
fn timer_timeout_supports_string_code_callback() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
setTimeout(
"document.getElementById('result').textContent = document.getElementById('result').textContent + 'Y';",
5
);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "")?;
h.advance_time(5)?;
h.assert_text("#result", "Y")?;
h.advance_time(20)?;
h.assert_text("#result", "Y")?;
Ok(())
}
#[test]
fn line_and_block_comments_are_ignored_in_script_parser() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
// top level comment
document.getElementById('btn').addEventListener('click', () => {
document.getElementById('result').textContent = 'A'; // inline comment
/* block comment */
document.getElementById('result').textContent =
document.getElementById('result').textContent + 'B';
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "AB")?;
Ok(())
}
#[test]
fn run_due_timers_runs_only_currently_due_tasks() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const result = document.getElementById('result');
setTimeout(() => {
result.textContent = result.textContent + 'A';
}, 0);
setTimeout(() => {
result.textContent = result.textContent + 'B';
}, 5);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
assert_eq!(h.now_ms(), 0);
let ran = h.run_due_timers()?;
assert_eq!(ran, 1);
assert_eq!(h.now_ms(), 0);
h.assert_text("#result", "A")?;
let ran = h.run_due_timers()?;
assert_eq!(ran, 0);
h.assert_text("#result", "A")?;
Ok(())
}
#[test]
fn run_due_timers_returns_zero_for_empty_queue() -> Result<()> {
let html = r#"<button id='btn'>run</button>"#;
let mut h = Harness::from_html(html)?;
assert_eq!(h.run_due_timers()?, 0);
Ok(())
}
#[test]
fn clear_timer_cancels_specific_pending_timer() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const result = document.getElementById('result');
setTimeout(() => {
result.textContent = result.textContent + 'A';
}, 5);
setTimeout(() => {
result.textContent = result.textContent + 'B';
}, 0);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
assert!(h.clear_timer(1));
assert!(!h.clear_timer(1));
assert!(!h.clear_timer(999));
h.advance_time(0)?;
h.assert_text("#result", "B")?;
h.advance_time(10)?;
h.assert_text("#result", "B")?;
Ok(())
}
#[test]
fn clear_all_timers_empties_pending_queue() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const result = document.getElementById('result');
setTimeout(() => {
result.textContent = 'A';
}, 0);
setInterval(() => {
result.textContent = 'B';
}, 5);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
assert_eq!(h.pending_timers().len(), 2);
assert_eq!(h.clear_all_timers(), 2);
assert!(h.pending_timers().is_empty());
h.flush()?;
h.assert_text("#result", "")?;
Ok(())
}
#[test]
fn run_next_due_timer_runs_only_one_due_task_without_advancing_clock() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const result = document.getElementById('result');
setTimeout(() => {
result.textContent = result.textContent + 'A';
}, 0);
setTimeout(() => {
result.textContent = result.textContent + 'B';
}, 5);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
assert_eq!(h.now_ms(), 0);
assert!(h.run_next_due_timer()?);
assert_eq!(h.now_ms(), 0);
h.assert_text("#result", "A")?;
assert!(!h.run_next_due_timer()?);
assert_eq!(h.now_ms(), 0);
h.assert_text("#result", "A")?;
Ok(())
}
#[test]
fn run_next_due_timer_returns_false_for_empty_queue() -> Result<()> {
let html = r#"<button id='btn'>run</button>"#;
let mut h = Harness::from_html(html)?;
assert!(!h.run_next_due_timer()?);
Ok(())
}
#[test]
fn pending_timers_returns_due_ordered_snapshot() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<script>
document.getElementById('btn').addEventListener('click', () => {
setTimeout(() => {}, 10);
setInterval(() => {}, 5);
setTimeout(() => {}, 0);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
let timers = h.pending_timers();
assert_eq!(
timers,
vec![
PendingTimer {
id: 3,
due_at: 0,
order: 2,
interval_ms: None,
},
PendingTimer {
id: 2,
due_at: 5,
order: 1,
interval_ms: Some(5),
},
PendingTimer {
id: 1,
due_at: 10,
order: 0,
interval_ms: None,
},
]
);
Ok(())
}
#[test]
fn pending_timers_reflects_advance_time_execution() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<script>
document.getElementById('btn').addEventListener('click', () => {
setInterval(() => {}, 5);
setTimeout(() => {}, 7);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.advance_time(5)?;
let timers = h.pending_timers();
assert_eq!(
timers,
vec![
PendingTimer {
id: 2,
due_at: 7,
order: 1,
interval_ms: None,
},
PendingTimer {
id: 1,
due_at: 10,
order: 2,
interval_ms: Some(5),
},
]
);
Ok(())
}
#[test]
fn run_next_timer_executes_single_task_in_due_order() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const result = document.getElementById('result');
setTimeout(() => {
result.textContent = result.textContent + 'A';
}, 10);
setTimeout(() => {
result.textContent = result.textContent + 'B';
}, 0);
setTimeout(() => {
result.textContent = result.textContent + 'C';
}, 10);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
assert_eq!(h.now_ms(), 0);
assert!(h.run_next_timer()?);
assert_eq!(h.now_ms(), 0);
h.assert_text("#result", "B")?;
assert!(h.run_next_timer()?);
assert_eq!(h.now_ms(), 10);
h.assert_text("#result", "BA")?;
assert!(h.run_next_timer()?);
assert_eq!(h.now_ms(), 10);
h.assert_text("#result", "BAC")?;
assert!(!h.run_next_timer()?);
assert_eq!(h.now_ms(), 10);
Ok(())
}
#[test]
fn advance_time_to_runs_due_timers_until_target() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const result = document.getElementById('result');
setTimeout(() => {
result.textContent = result.textContent + 'A';
}, 5);
setTimeout(() => {
result.textContent = result.textContent + 'B';
}, 10);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.advance_time_to(7)?;
assert_eq!(h.now_ms(), 7);
h.assert_text("#result", "A")?;
h.advance_time_to(10)?;
assert_eq!(h.now_ms(), 10);
h.assert_text("#result", "AB")?;
h.advance_time_to(10)?;
assert_eq!(h.now_ms(), 10);
h.assert_text("#result", "AB")?;
Ok(())
}
#[test]
fn advance_time_to_rejects_past_target() -> Result<()> {
let html = r#"<button id='btn'>run</button>"#;
let mut h = Harness::from_html(html)?;
h.advance_time(3)?;
let err = h
.advance_time_to(2)
.expect_err("advance_time_to with past target should fail");
match err {
Error::ScriptRuntime(msg) => {
assert!(msg.contains("advance_time_to requires target >= now_ms"));
assert!(msg.contains("target=2"));
assert!(msg.contains("now_ms=3"));
}
other => panic!("unexpected error: {other:?}"),
}
Ok(())
}
#[test]
fn set_timeout_respects_delay_order_and_nested_queueing() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const result = document.getElementById('result');
setTimeout(() => {
result.textContent = result.textContent + '1';
}, 10);
setTimeout(() => {
result.textContent = result.textContent + '0';
setTimeout(() => {
result.textContent = result.textContent + 'N';
});
}, 0);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "")?;
h.flush()?;
h.assert_text("#result", "0N1")?;
Ok(())
}
#[test]
fn queue_microtask_runs_after_synchronous_task_body() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const result = document.getElementById('result');
result.textContent = 'A';
queueMicrotask(() => {
result.textContent = result.textContent + 'B';
});
result.textContent = result.textContent + 'C';
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "ACB")?;
Ok(())
}
#[test]
fn queue_microtask_window_method_supports_function_reference_and_returns_undefined() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const result = document.getElementById('result');
function appendB() {
result.textContent = result.textContent + 'B';
}
const api = window.queueMicrotask;
const returned = api(appendB);
result.textContent = String(returned === undefined) + ':A';
window.queueMicrotask(() => {
result.textContent = result.textContent + 'C';
});
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "true:ABC")?;
Ok(())
}
#[test]
fn queue_microtask_requires_callable_callback() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<script>
document.getElementById('btn').addEventListener('click', () => {
queueMicrotask(42);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
match h.click("#btn") {
Err(Error::ScriptRuntime(message)) => {
assert!(
message.contains("queueMicrotask callback must be callable"),
"unexpected runtime error message: {message}"
);
}
other => panic!("expected runtime error, got: {other:?}"),
}
Ok(())
}
#[test]
fn promise_then_microtask_runs_before_next_timer() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const result = document.getElementById('result');
result.textContent = 'A';
Promise.resolve().then(() => {
result.textContent = result.textContent + 'P';
});
setTimeout(() => {
result.textContent = result.textContent + 'T';
}, 0);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "AP")?;
h.flush()?;
h.assert_text("#result", "APT")?;
Ok(())
}
#[test]
fn fake_time_advance_controls_timer_execution() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const result = document.getElementById('result');
setTimeout(() => {
result.textContent = result.textContent + '0';
}, 0);
setTimeout(() => {
result.textContent = result.textContent + '1';
}, 10);
setTimeout(() => {
result.textContent = result.textContent + '2';
}, 20);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "")?;
assert_eq!(h.now_ms(), 0);
h.advance_time(0)?;
h.assert_text("#result", "0")?;
assert_eq!(h.now_ms(), 0);
h.advance_time(9)?;
h.assert_text("#result", "0")?;
assert_eq!(h.now_ms(), 9);
h.advance_time(1)?;
h.assert_text("#result", "01")?;
assert_eq!(h.now_ms(), 10);
h.advance_time(10)?;
h.assert_text("#result", "012")?;
assert_eq!(h.now_ms(), 20);
Ok(())
}
#[test]
fn fake_time_advance_runs_interval_ticks_by_due_time() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const result = document.getElementById('result');
const id = setInterval(() => {
result.textContent = result.textContent + 'I';
if (result.textContent === 'III') clearInterval(id);
}, 5);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "")?;
h.advance_time(4)?;
h.assert_text("#result", "")?;
h.advance_time(1)?;
h.assert_text("#result", "I")?;
h.advance_time(10)?;
h.assert_text("#result", "III")?;
h.advance_time(100)?;
h.assert_text("#result", "III")?;
Ok(())
}
#[test]
fn date_now_uses_fake_clock_for_handlers_and_timers() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const result = document.getElementById('result');
result.textContent = Date.now() + ':';
setTimeout(() => {
result.textContent = result.textContent + Date.now();
}, 10);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.advance_time(7)?;
h.click("#btn")?;
h.assert_text("#result", "7:")?;
h.advance_time(9)?;
h.assert_text("#result", "7:")?;
h.advance_time(1)?;
h.assert_text("#result", "7:17")?;
Ok(())
}
#[test]
fn date_now_with_flush_advances_to_timer_due_time() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const result = document.getElementById('result');
result.textContent = Date.now();
setTimeout(() => {
result.textContent = result.textContent + ':' + Date.now();
}, 25);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "0")?;
h.flush()?;
h.assert_text("#result", "0:25")?;
assert_eq!(h.now_ms(), 25);
Ok(())
}
#[test]
fn performance_now_uses_fake_clock_for_handlers_and_timers() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const result = document.getElementById('result');
result.textContent = performance.now() + ':' + window.performance.now();
setTimeout(() => {
result.textContent = result.textContent + ':' + performance.now();
}, 10);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.advance_time(7)?;
h.click("#btn")?;
h.assert_text("#result", "7:7")?;
h.advance_time(9)?;
h.assert_text("#result", "7:7")?;
h.advance_time(1)?;
h.assert_text("#result", "7:7:17")?;
Ok(())
}
#[test]
fn date_constructor_and_static_methods_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const nowDate = new Date();
const fromNumber = new Date(1000);
const parsed = Date.parse('1970-01-01T00:00:02Z');
const utc = Date.UTC(1970, 0, 1, 0, 0, 3);
const parsedViaWindow = window.Date.parse('1970-01-01');
document.getElementById('result').textContent =
nowDate.getTime() + ':' + fromNumber.getTime() + ':' +
parsed + ':' + utc + ':' + parsedViaWindow;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.advance_time(42)?;
h.click("#btn")?;
h.assert_text("#result", "42:1000:2000:3000:0")?;
Ok(())
}
#[test]
fn date_instance_methods_and_set_time_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const d = new Date('2024-03-05T01:02:03Z');
const y = d.getFullYear();
const m = d.getMonth();
const day = d.getDate();
const h = d.getHours();
const min = d.getMinutes();
const s = d.getSeconds();
const iso = d.toISOString();
const updated = d.setTime(Date.UTC(1970, 0, 2, 3, 4, 5));
const iso2 = d.toISOString();
document.getElementById('result').textContent =
y + ':' + m + ':' + day + ':' + h + ':' + min + ':' + s +
'|' + iso + '|' + updated + '|' + iso2;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"2024:2:5:1:2:3|2024-03-05T01:02:03.000Z|97445000|1970-01-02T03:04:05.000Z",
)?;
Ok(())
}
#[test]
fn date_string_hint_and_raw_getter_coercion_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const date = new Date(0);
const proto = Object.getPrototypeOf(date);
const toStringFn = date['toString'];
const valueOfFn = date['valueOf'];
document.getElementById('result').textContent = [
String(String(date) === date['toString']()),
String(String.prototype.includes.call(date, '1970')),
String(RegExp.prototype[Symbol.search].call(/1970/, date) === 0),
String(toStringFn === proto['toString']),
String(valueOfFn === proto['valueOf']),
String(toStringFn.call(date) === date.toISOString()),
String(valueOfFn.call(date) === 0),
String(toStringFn.name === 'toString'),
String(valueOfFn.length === 0),
String(Function.prototype.toString.call(toStringFn) === toStringFn.toString())
].join('|');
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"true|true|true|true|true|true|true|true|true|true",
)?;
Ok(())
}
#[test]
fn date_instance_method_member_call_supports_constructor_expression_target() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const iso = new Date('2024-03-05T01:02:03Z').toISOString();
document.getElementById('result').textContent = iso;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "2024-03-05T01:02:03.000Z")?;
Ok(())
}
#[test]
fn date_get_utc_full_year_handles_timezone_boundaries() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const date1 = new Date('1975-12-31T23:15:30+11:00');
const date2 = new Date('1975-12-31T23:15:30-11:00');
const direct = new Date('1975-12-31T23:15:30-11:00').getUTCFullYear();
document.getElementById('result').textContent =
date1.getUTCFullYear() + ':' + date2.getUTCFullYear() + ':' + direct;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "1975:1976:1976")?;
Ok(())
}
#[test]
fn date_parse_invalid_input_returns_nan_and_utc_normalizes_overflow() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const parsedValue = Date.parse('invalid-date');
const isInvalid = isNaN(parsedValue);
const ts = Date.UTC(2020, 12, 1, 25, 61, 61);
const normalizedDate = new Date(ts);
const normalized = normalizedDate.toISOString();
document.getElementById('result').textContent =
isInvalid + ':' + normalized;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "true:2021-01-02T02:02:01.000Z")?;
Ok(())
}
#[test]
fn date_constructor_supports_component_arguments() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const d = new Date(2026, 0, 31, 12, 34, 56, 789);
document.getElementById('result').textContent = [
d.toISOString(),
d.getFullYear(),
d.getMonth(),
d.getDate(),
d.getHours(),
d.getMinutes(),
d.getSeconds()
].join(':');
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "2026-01-31T12:34:56.789Z:2026:0:31:12:34:56")?;
Ok(())
}
#[test]
fn date_method_arity_errors_have_stable_messages() {
let cases = [
(
"<script>new Date(1, 2, 3, 4, 5, 6, 7, 8);</script>",
"new Date supports up to seven arguments",
),
(
"<script>Date.parse();</script>",
"Date.parse requires exactly one argument",
),
(
"<script>Date.UTC(1970);</script>",
"Date.UTC requires between 2 and 7 arguments",
),
(
"<script>Date.UTC(1970, , 1);</script>",
"Date.UTC argument cannot be empty",
),
(
"<script>const d = new Date(); d.getTime(1);</script>",
"getTime does not take arguments",
),
(
"<script>const d = new Date(); d.setTime();</script>",
"setTime requires exactly one argument",
),
(
"<script>const d = new Date(); d.toISOString(1);</script>",
"toISOString does not take arguments",
),
(
"<script>const d = new Date(); d.getUTCFullYear(1);</script>",
"getUTCFullYear does not take arguments",
),
];
for (html, expected) in cases {
let err = Harness::from_html(html).expect_err("script should fail to parse");
match err {
Error::ScriptParse(msg) => {
assert!(msg.contains(expected), "expected '{expected}' in '{msg}'")
}
other => panic!("unexpected error: {other:?}"),
}
}
}
#[test]
fn math_constants_and_symbol_to_string_tag_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
document.getElementById('result').textContent =
Math.round(Math.E * 1000) + ':' +
Math.round(Math.LN10 * 1000) + ':' +
Math.round(Math.LN2 * 1000) + ':' +
Math.round(Math.LOG10E * 1000) + ':' +
Math.round(Math.LOG2E * 1000) + ':' +
Math.round(Math.PI * 1000) + ':' +
Math.round(Math.SQRT1_2 * 1000) + ':' +
Math.round(Math.SQRT2 * 1000) + ':' +
(window.Math.PI === Math.PI) + ':' +
Math[Symbol.toStringTag];
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "2718:2303:693:434:1443:3142:707:1414:true:Math")?;
Ok(())
}
#[test]
fn math_static_methods_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
document.getElementById('result').textContent =
Math.abs(-3.5) + ':' +
Math.acos(1) + ':' +
Math.acosh(1) + ':' +
Math.round(Math.asin(1) * 1000) + ':' +
Math.asinh(0) + ':' +
Math.round(Math.atan(1) * 1000) + ':' +
Math.round(Math.atan2(1, 1) * 1000) + ':' +
Math.round(Math.atanh(0.5) * 1000000) + ':' +
Math.cbrt(27) + ':' +
Math.ceil(1.2) + ':' +
Math.clz32(1) + ':' +
Math.cos(0) + ':' +
Math.cosh(0) + ':' +
Math.round(Math.exp(1) * 1000) + ':' +
Math.round(Math.expm1(1) * 1000) + ':' +
Math.floor(1.8) + ':' +
Math.round(Math.f16round(1.337) * 1000000) + ':' +
Math.round(Math.fround(1.337) * 1000000) + ':' +
Math.hypot(3, 4) + ':' +
Math.imul(2147483647, 2) + ':' +
Math.round(Math.log(Math.E) * 1000) + ':' +
Math.log10(1000) + ':' +
Math.round(Math.log1p(1) * 1000) + ':' +
Math.log2(8) + ':' +
Math.max(1, 5, 3) + ':' +
Math.min(1, 5, 3) + ':' +
Math.pow(2, 8) + ':' +
Math.round(1.5) + ':' +
Math.round(-1.5) + ':' +
Math.sign(-3) + ':' +
Math.round(Math.sin(Math.PI / 2) * 1000) + ':' +
Math.sinh(0) + ':' +
Math.sqrt(9) + ':' +
Math.sumPrecise([1, 2, 3]) + ':' +
Math.tan(0) + ':' +
Math.tanh(0) + ':' +
Math.trunc(-1.9) + ':' +
isNaN(Math.sign(NaN)) + ':' +
Math.hypot() + ':' +
isFinite(Math.max()) + ':' +
isFinite(Math.min());
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"3.5:0:0:1571:0:785:785:549306:3:2:31:1:1:2718:1718:1:1336914:1337000:5:-2:1000:3:693:3:5:1:256:2:-1:-1:1000:0:3:6:0:0:-1:true:0:false:false",
)?;
Ok(())
}
#[test]
fn math_sum_precise_requires_array_argument() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<script>
document.getElementById('btn').addEventListener('click', () => {
Math.sumPrecise(1);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
let err = h
.click("#btn")
.expect_err("Math.sumPrecise should reject non-array argument");
match err {
Error::ScriptRuntime(msg) => {
assert!(msg.contains("Math.sumPrecise argument must be an array"))
}
other => panic!("unexpected Math.sumPrecise error: {other:?}"),
}
Ok(())
}
#[test]
fn math_method_arity_errors_have_stable_messages() {
let cases = [
(
"<script>Math.abs();</script>",
"Math.abs requires exactly one argument",
),
(
"<script>Math.random(1);</script>",
"Math.random does not take arguments",
),
(
"<script>Math.atan2(1);</script>",
"Math.atan2 requires exactly two arguments",
),
(
"<script>Math.imul(1);</script>",
"Math.imul requires exactly two arguments",
),
(
"<script>Math.pow(2);</script>",
"Math.pow requires exactly two arguments",
),
(
"<script>Math.sumPrecise();</script>",
"Math.sumPrecise requires exactly one argument",
),
(
"<script>Math.max(1, , 2);</script>",
"Math.max argument cannot be empty",
),
];
for (html, expected) in cases {
let err = Harness::from_html(html).expect_err("script should fail to parse");
match err {
Error::ScriptParse(msg) => {
assert!(msg.contains(expected), "expected '{expected}' in '{msg}'")
}
other => panic!("unexpected error: {other:?}"),
}
}
}
#[test]
fn math_random_is_deterministic_with_seed() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
document.getElementById('result').textContent =
Math.random() + ':' + Math.random() + ':' + Math.random();
});
</script>
"#;
let mut h1 = Harness::from_html(html)?;
let mut h2 = Harness::from_html(html)?;
h1.set_random_seed(12345);
h2.set_random_seed(12345);
h1.click("#btn")?;
h2.click("#btn")?;
let out1 = h1.dump_dom("#result")?;
let out2 = h2.dump_dom("#result")?;
assert_eq!(out1, out2);
Ok(())
}
#[test]
fn math_random_returns_unit_interval() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const r = Math.random();
if (r >= 0 && r < 1) document.getElementById('result').textContent = 'ok';
else document.getElementById('result').textContent = 'ng';
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.set_random_seed(42);
h.click("#btn")?;
h.assert_text("#result", "ok")?;
Ok(())
}
#[test]
fn number_constructor_and_static_properties_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const a = Number('123');
const b = Number('12.3');
const c = Number('');
const d = Number(null);
const e = Number('0x11');
const f = Number('0b11');
const g = Number('0o11');
const h = Number('-Infinity') === Number.NEGATIVE_INFINITY;
const i = Number('foo');
const j = new Number('5');
const k = Number.MAX_SAFE_INTEGER === 9007199254740991;
const l = Number.POSITIVE_INFINITY === Infinity;
const m = Number.NEGATIVE_INFINITY === -Infinity;
const n = Number.MIN_VALUE > 0;
const o = Number.MAX_VALUE > 1e300;
const p = Number.EPSILON > 0 && Number.EPSILON < 1;
const q = Number.NaN === Number.NaN;
const r = window.Number.MAX_SAFE_INTEGER === Number.MAX_SAFE_INTEGER;
document.getElementById('result').textContent =
a + ':' + b + ':' + c + ':' + d + ':' + e + ':' + f + ':' + g + ':' +
h + ':' + (i === i) + ':' + j + ':' + k + ':' + l + ':' + m + ':' +
n + ':' + o + ':' + p + ':' + q + ':' + r;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"123:12.3:0:0:17:3:9:true:false:5:true:true:true:true:true:true:false:true",
)?;
Ok(())
}
#[test]
fn number_static_methods_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const a = Number.isFinite(1 / 3);
const b = Number.isFinite('1');
const c = Number.isInteger(3);
const d = Number.isInteger(3.1);
const e = Number.isNaN(NaN);
const f = Number.isNaN('NaN');
const g = Number.isSafeInteger(9007199254740991);
const h = Number.isSafeInteger(9007199254740992);
const i = Number.parseFloat('3.5px');
const j = Number.parseInt('10', 2);
const k = window.Number.parseInt('0x10', 16);
document.getElementById('result').textContent =
a + ':' + b + ':' + c + ':' + d + ':' + e + ':' + f + ':' +
g + ':' + h + ':' + i + ':' + j + ':' + k;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"true:false:true:false:true:false:true:false:3.5:2:16",
)?;
Ok(())
}
#[test]
fn number_instance_methods_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const n = Number('255');
document.getElementById('result').textContent =
(12.34).toFixed() + ':' +
(12.34).toFixed(1) + ':' +
(12.34).toExponential() + ':' +
(12.34).toExponential(2) + ':' +
(12.34).toPrecision() + ':' +
(12.34).toPrecision(3) + ':' +
n.toString(16) + ':' +
n.toString() + ':' +
(1.5).toString(2) + ':' +
(1.5).toLocaleString() + ':' +
(1.5).valueOf();
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"12:12.3:1.234e+1:1.23e+1:12.34:12.3:ff:255:1.1:1.5:1.5",
)?;
Ok(())
}
#[test]
fn number_to_fixed_matches_reference_examples() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
function financial(x) {
return Number.parseFloat(x).toFixed(2);
}
document.getElementById('btn').addEventListener('click', () => {
document.getElementById('result').textContent = [
financial(123.456),
financial(0.004),
financial("1.23e+5"),
(1.23e20).toFixed(2),
(1e21).toFixed(2),
(6.02 * 10 ** 23).toFixed(50),
(Infinity).toFixed(2),
(-Infinity).toFixed(2),
(NaN).toFixed(2),
].join('|');
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"123.46|0.00|123000.00|123000000000000000000.00|1e+21|6.019999999999999e+23|Infinity|-Infinity|NaN",
)?;
Ok(())
}
#[test]
fn number_to_locale_string_honors_fraction_digit_options() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const a = (12.3456).toLocaleString(undefined, {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
});
const b = (1200).toLocaleString('de-DE', { minimumFractionDigits: 2 });
document.getElementById('result').textContent = a + ':' + b;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "12.35:1.200,00")?;
Ok(())
}
#[test]
fn number_method_arity_errors_have_stable_messages() {
let cases = [
(
"<script>Number(1, 2);</script>",
"Number supports zero or one argument",
),
(
"<script>Number.isFinite();</script>",
"Number.isFinite requires exactly one argument",
),
(
"<script>window.Number.parseInt();</script>",
"Number.parseInt requires one or two arguments",
),
(
"<script>Number.parseInt('10', );</script>",
"Number.parseInt radix argument cannot be empty",
),
(
"<script>(1).toFixed(1, 2);</script>",
"toFixed supports at most one argument",
),
(
"<script>(1).toLocaleString(1, 2, 3);</script>",
"toLocaleString supports at most two arguments",
),
(
"<script>(1).valueOf(1);</script>",
"valueOf does not take arguments",
),
];
for (html, expected) in cases {
let err = Harness::from_html(html).expect_err("script should fail to parse");
match err {
Error::ScriptParse(msg) => {
assert!(msg.contains(expected), "expected '{expected}' in '{msg}'")
}
other => panic!("unexpected error: {other:?}"),
}
}
}
#[test]
fn number_instance_method_runtime_range_errors_are_reported() -> Result<()> {
let html = r#"
<button id='fixed'>fixed</button>
<button id='string'>string</button>
<script>
document.getElementById('fixed').addEventListener('click', () => {
(1).toFixed(101);
});
document.getElementById('string').addEventListener('click', () => {
(1).toString(1);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
let fixed_err = h
.click("#fixed")
.expect_err("toFixed should reject out-of-range fractionDigits");
match fixed_err {
Error::ScriptRuntime(msg) => {
assert!(msg.contains("toFixed fractionDigits must be between 0 and 100"))
}
other => panic!("unexpected toFixed error: {other:?}"),
}
let string_err = h
.click("#string")
.expect_err("toString should reject out-of-range radix");
match string_err {
Error::ScriptRuntime(msg) => {
assert!(msg.contains("toString radix must be between 2 and 36"))
}
other => panic!("unexpected toString error: {other:?}"),
}
Ok(())
}
#[test]
fn number_to_fixed_rejects_non_number_receiver() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<script>
document.getElementById('btn').addEventListener('click', () => {
(true).toFixed(2);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
let err = h
.click("#btn")
.expect_err("toFixed should reject non-number receivers");
match err {
Error::ScriptRuntime(msg) => {
assert!(msg.contains("TypeError"));
assert!(msg.contains("toFixed"));
}
other => panic!("unexpected toFixed receiver error: {other:?}"),
}
Ok(())
}
#[test]
fn intl_date_time_and_number_format_examples_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const count = 26254.39;
const date = new Date('2012-05-24');
const us =
new Intl.DateTimeFormat('en-US').format(date) + ' ' +
new Intl.NumberFormat('en-US').format(count);
const de =
new Intl.DateTimeFormat('de-DE').format(date) + ' ' +
new Intl.NumberFormat('de-DE').format(count);
document.getElementById('result').textContent = us + '|' + de;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "5/24/2012 26,254.39|24.5.2012 26.254,39")?;
Ok(())
}
#[test]
fn intl_number_format_honors_fraction_digits_options() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const v1 = 28.000000000000004;
const v2 = 31.799999999999997;
const formatter = new Intl.NumberFormat('en', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
const format = formatter.format;
document.getElementById('result').textContent =
formatter.format(v1) + '|' + format(v2);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "28.00|31.80")?;
Ok(())
}
#[test]
fn intl_uses_navigator_language_preferences() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const date = new Date('2012-05-24');
const formattedDate = new Intl.DateTimeFormat(navigator.language).format(date);
const formattedCount = new Intl.NumberFormat(navigator.languages).format(26254.39);
document.getElementById('result').textContent = formattedDate + '|' + formattedCount;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "5/24/2012|26,254.39")?;
Ok(())
}
#[test]
fn intl_static_methods_and_to_string_tag_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const canonical = Intl.getCanonicalLocales(['EN-us', 'de-de', 'EN-us']);
const currencies = Intl.supportedValuesOf('currency');
document.getElementById('result').textContent =
canonical.join(',') + '|' + currencies.join(',') + '|' + Intl[Symbol.toStringTag];
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "en-US,de-DE|EUR,JPY,USD|Intl")?;
Ok(())
}
#[test]
fn intl_get_canonical_locales_examples_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const one = Intl.getCanonicalLocales('EN-US');
const two = Intl.getCanonicalLocales(['EN-US', 'Fr']);
document.getElementById('result').textContent = one.join(',') + '|' + two.join(',');
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "en-US|en-US,fr")?;
let html_error = r#"
<button id='btn'>run</button>
<script>
document.getElementById('btn').addEventListener('click', () => {
Intl.getCanonicalLocales('EN_US');
});
</script>
"#;
let mut h = Harness::from_html(html_error)?;
let err = h
.click("#btn")
.expect_err("invalid language tag should throw");
match err {
Error::ScriptRuntime(msg) => {
assert_eq!(msg, "RangeError: invalid language tag: \"EN_US\"")
}
other => panic!("unexpected error: {other:?}"),
}
Ok(())
}
#[test]
fn intl_supported_values_of_examples_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const calendar = Intl.supportedValuesOf('calendar');
const collation = Intl.supportedValuesOf('collation');
const currency = Intl.supportedValuesOf('currency');
const numberingSystem = Intl.supportedValuesOf('numberingSystem');
const timeZone = Intl.supportedValuesOf('timeZone');
const unit = Intl.supportedValuesOf('unit');
document.getElementById('result').textContent =
calendar.join(',') + '|' +
collation.join(',') + '|' +
currency.join(',') + '|' +
numberingSystem.join(',') + '|' +
timeZone.join(',') + '|' +
unit.join(',');
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"gregory,islamic-umalqura,japanese|default,emoji,phonebk|EUR,JPY,USD|arab,latn,thai|Africa/Cairo,America/Los_Angeles,America/New_York,Asia/Jerusalem,Asia/Kolkata,Asia/Seoul,Asia/Shanghai,Asia/Tokyo,Australia/Sydney,Europe/Berlin,Europe/London,Europe/Paris,UTC|day,hour,meter,minute,month,second,week,year",
)?;
let html_error = r#"
<button id='btn'>run</button>
<script>
document.getElementById('btn').addEventListener('click', () => {
Intl.supportedValuesOf('someInvalidKey');
});
</script>
"#;
let mut h = Harness::from_html(html_error)?;
let err = h
.click("#btn")
.expect_err("invalid supportedValuesOf key should throw");
match err {
Error::ScriptRuntime(msg) => {
assert_eq!(msg, "RangeError: invalid key: \"someInvalidKey\"")
}
other => panic!("unexpected error: {other:?}"),
}
Ok(())
}
#[test]
fn intl_namespace_is_not_a_constructor() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<script>
document.getElementById('btn').addEventListener('click', () => {
const i = new Intl();
});
</script>
"#;
let mut h = Harness::from_html(html)?;
let err = h.click("#btn").expect_err("new Intl should fail");
match err {
Error::ScriptRuntime(msg) => assert!(msg.contains("Intl is not a constructor")),
other => panic!("unexpected error: {other:?}"),
}
Ok(())
}
#[test]
fn intl_date_time_format_try_it_examples_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const date = new Date(Date.UTC(2020, 11, 20, 3, 23, 16, 738));
const us = new Intl.DateTimeFormat('en-US').format(date);
const fallback = new Intl.DateTimeFormat(['ban', 'id']).format(date);
const styled = new Intl.DateTimeFormat('en-GB', {
dateStyle: 'full',
timeStyle: 'long',
timeZone: 'Australia/Sydney',
}).format(date);
document.getElementById('result').textContent = us + '|' + fallback + '|' + styled;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"12/20/2020|20/12/2020|Sunday, 20 December 2020 at 14:23:16 GMT+11",
)?;
Ok(())
}
#[test]
fn intl_date_time_format_instance_methods_and_getter_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const dtf = new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
timeZone: 'UTC'
});
const d1 = new Date(Date.UTC(2020, 11, 20, 3, 23, 16, 738));
const d2 = new Date(Date.UTC(2020, 11, 21, 3, 23, 16, 738));
const fmt = dtf.format;
const fromGetter = fmt(d1);
const parts = dtf.formatToParts(d1);
const range = dtf.formatRange(d1, d2);
const rangeParts = dtf.formatRangeToParts(d1, d2);
const partsOk = JSON.stringify(parts).includes('"type":"month"');
const rangePartsOk =
JSON.stringify(rangeParts).includes('"source":"startRange"') &&
JSON.stringify(rangeParts).includes('"source":"endRange"');
document.getElementById('result').textContent =
fromGetter + '|' + range + '|' + partsOk + ':' + rangePartsOk;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "12/20/2020|12/20/2020 - 12/21/2020|true:true")?;
Ok(())
}
#[test]
fn intl_date_time_format_supported_locales_and_resolved_options_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const supportedLocales = Intl.DateTimeFormat.supportedLocalesOf(['ban', 'id', 'en-GB', 'fr']);
const supported = supportedLocales.join(',');
const ro = new Intl.DateTimeFormat('ja-JP-u-ca-japanese', {
numberingSystem: 'arab',
timeZone: 'America/Los_Angeles',
dateStyle: 'short',
}).resolvedOptions();
const tag = Intl.DateTimeFormat.prototype[Symbol.toStringTag];
const ar = new Intl.DateTimeFormat('ar-EG').format(new Date(Date.UTC(2012, 11, 20, 3, 0, 0)));
document.getElementById('result').textContent =
supported + '|' + ro.locale + ':' + ro.calendar + ':' + ro.numberingSystem + ':' +
ro.timeZone + ':' + ro.dateStyle + '|' + tag + '|' + ar;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"id,en-GB|ja-JP-u-ca-japanese:japanese:arab:America/Los_Angeles:short|Intl.DateTimeFormat|٢٠/ٔ٢/٢٠ٔ٢",
)?;
Ok(())
}
#[test]
fn intl_date_time_format_using_locales_examples_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));
const us = new Intl.DateTimeFormat('en-US').format(date);
const gb = new Intl.DateTimeFormat('en-GB').format(date);
const ko = new Intl.DateTimeFormat('ko-KR').format(date);
const ar = new Intl.DateTimeFormat('ar-EG').format(date);
const ja = new Intl.DateTimeFormat('ja-JP-u-ca-japanese').format(date);
const fallback = new Intl.DateTimeFormat(['ban', 'id']).format(date);
document.getElementById('result').textContent =
us + '|' + gb + '|' + ko + '|' + ar + '|' + ja + '|' + fallback;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"12/20/2012|20/12/2012|2012. 12. 20.|٢٠/ٔ٢/٢٠ٔ٢|24/12/20|20/12/2012",
)?;
Ok(())
}
#[test]
fn intl_date_time_format_using_options_examples_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0, 200));
let options = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
};
const de = new Intl.DateTimeFormat('de-DE', options).format(date);
options.timeZone = 'UTC';
options.timeZoneName = 'short';
const usWithUtc = new Intl.DateTimeFormat('en-US', options).format(date);
options = {
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
timeZone: 'Australia/Sydney',
timeZoneName: 'short',
};
const au = new Intl.DateTimeFormat('en-AU', options).format(date);
options.fractionalSecondDigits = 3;
const auPrecise = new Intl.DateTimeFormat('en-AU', options).format(date);
options = {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
hour12: false,
timeZone: 'America/Los_Angeles',
};
const us24 = new Intl.DateTimeFormat('en-US', options).format(date);
const defaultLocale = new Intl.DateTimeFormat(undefined, options).format(date);
const dayPeriod = new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
dayPeriod: 'short',
timeZone: 'UTC',
}).format(new Date(Date.UTC(2020, 11, 20, 22, 0, 0)));
const constructorOk =
Intl.DateTimeFormat.prototype.constructor === Intl.DateTimeFormat;
document.getElementById('result').textContent =
de + '|' + usWithUtc + '|' + au + '|' + auPrecise + '|' + us24 + '|' +
defaultLocale + '|' + dayPeriod + '|' + constructorOk;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"Donnerstag, 20. Dezember 2012|Thursday, December 20, 2012, GMT|2:00:00 pm AEDT|2:00:00.200 pm AEDT|12/19/2012, 19:00:00|12/19/2012, 19:00:00|10 at night|true",
)?;
Ok(())
}
#[test]
fn intl_number_format_try_it_examples_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const number = 123456.789;
const de = new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
}).format(number);
const ja = new Intl.NumberFormat('ja-JP', {
style: 'currency',
currency: 'JPY',
}).format(number);
const enIn = new Intl.NumberFormat('en-IN', {
maximumSignificantDigits: 3,
}).format(number);
document.getElementById('result').textContent = de + '|' + ja + '|' + enIn;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "123.456,79 ā¬|ļæ„123,457|1,23,000")?;
Ok(())
}
#[test]
fn intl_number_format_using_locales_examples_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const number = 123456.789;
const de = new Intl.NumberFormat('de-DE').format(number);
const ar = new Intl.NumberFormat('ar-EG').format(number);
const enIn = new Intl.NumberFormat('en-IN').format(number);
const hanidec = new Intl.NumberFormat('zh-Hans-CN-u-nu-hanidec').format(number);
const fallback = new Intl.NumberFormat(['ban', 'id']).format(number);
document.getElementById('result').textContent =
de + '|' + ar + '|' + enIn + '|' + hanidec + '|' + fallback;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"123.456,789|ٔ٢٣٬٤ل٦٫٧٨٩|1,23,456.789|äøäŗäø,åäŗå
.äøå
«ä¹|123.456,789",
)?;
Ok(())
}
#[test]
fn intl_number_format_instance_methods_and_static_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const nf = new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
});
const fmt = nf.format;
const fromGetter = fmt(1234.5);
const parts = nf.formatToParts(1234.5);
const range = nf.formatRange(1, 2);
const rangeParts = nf.formatRangeToParts(1, 2);
const partsOk =
JSON.stringify(parts).includes('"type":"integer"') &&
JSON.stringify(parts).includes('"type":"currency"');
const rangePartsOk =
JSON.stringify(rangeParts).includes('"source":"startRange"') &&
JSON.stringify(rangeParts).includes('"source":"endRange"');
const supported = Intl.NumberFormat.supportedLocalesOf(['ban', 'id', 'en-IN', 'fr']).join(',');
const ro = nf.resolvedOptions();
const tag = Intl.NumberFormat.prototype[Symbol.toStringTag];
const ctor = nf.constructor === Intl.NumberFormat;
document.getElementById('result').textContent =
supported + '|' + fromGetter + '|' + partsOk + ':' + range + ':' + rangePartsOk + '|' +
ro.locale + ':' + ro.numberingSystem + ':' + ro.style + ':' + ro.currency + ':' +
ro.maximumFractionDigits + '|' + tag + '|' + ctor;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"id,en-IN|1.234,50 ā¬|true:1,00 ⬠- 2,00 ā¬:true|de-DE:latn:currency:EUR:2|Intl.NumberFormat|true",
)?;
Ok(())
}
#[test]
fn intl_number_format_unit_examples_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const speed = new Intl.NumberFormat('pt-PT', {
style: 'unit',
unit: 'kilometer-per-hour',
}).format(50);
const litres = (16).toLocaleString('en-GB', {
style: 'unit',
unit: 'liter',
unitDisplay: 'long',
});
document.getElementById('result').textContent = speed + '|' + litres;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "50 km/h|16 litres")?;
Ok(())
}
#[test]
fn intl_duration_format_examples_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const duration = {
hours: 1,
minutes: 46,
seconds: 40,
};
const fr = new Intl.DurationFormat('fr-FR', { style: 'long' }).format(duration);
const en = new Intl.DurationFormat('en', { style: 'short' }).format(duration);
const pt = new Intl.DurationFormat('pt', { style: 'narrow' }).format(duration);
document.getElementById('result').textContent = fr + '|' + en + '|' + pt;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"1 heure, 46 minutes et 40 secondes|1 hr, 46 min and 40 sec|1 h 46 min 40 s",
)?;
Ok(())
}
#[test]
fn intl_duration_format_instance_methods_and_static_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const duration = {
hours: 1,
minutes: 2,
seconds: 3,
};
const df = new Intl.DurationFormat('en', { style: 'short' });
const fmt = df.format;
const fromGetter = fmt(duration);
const parts = df.formatToParts(duration);
const partsOk =
JSON.stringify(parts).includes('"type":"hour"') &&
JSON.stringify(parts).includes('"type":"literal"');
const supported = Intl.DurationFormat.supportedLocalesOf(['fr-FR', 'en', 'pt', 'de']);
const ro = new Intl.DurationFormat('pt', { style: 'narrow' }).resolvedOptions();
const tag = Intl.DurationFormat.prototype[Symbol.toStringTag];
document.getElementById('result').textContent =
supported.join(',') + '|' + fromGetter + '|' + partsOk + '|' +
ro.locale + ':' + ro.style + '|' + tag;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"fr-FR,en,pt|1 hr, 2 min and 3 sec|true|pt:narrow|Intl.DurationFormat",
)?;
Ok(())
}
#[test]
fn intl_list_format_try_it_examples_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const vehicles = ['Motorcycle', 'Bus', 'Car'];
const formatter = new Intl.ListFormat('en', {
style: 'long',
type: 'conjunction',
});
const formatter2 = new Intl.ListFormat('de', {
style: 'short',
type: 'disjunction',
});
const formatter3 = new Intl.ListFormat('en', { style: 'narrow', type: 'unit' });
document.getElementById('result').textContent =
formatter.format(vehicles) + '|' +
formatter2.format(vehicles) + '|' +
formatter3.format(vehicles);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"Motorcycle, Bus, and Car|Motorcycle, Bus oder Car|Motorcycle Bus Car",
)?;
Ok(())
}
#[test]
fn intl_list_format_methods_and_options_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const list = ['Motorcycle', 'Bus', 'Car'];
const long = new Intl.ListFormat('en-GB', { style: 'long', type: 'conjunction' }).format(list);
const short = new Intl.ListFormat('en-GB', { style: 'short', type: 'disjunction' }).format(list);
const narrow = new Intl.ListFormat('en-GB', { style: 'narrow', type: 'unit' }).format(list);
const parts = new Intl.ListFormat('en-GB', {
style: 'long',
type: 'conjunction',
}).formatToParts(list);
const partsOk =
JSON.stringify(parts).includes('"type":"element"') &&
JSON.stringify(parts).includes('"value":" and "');
const supportedLocales = Intl.ListFormat.supportedLocalesOf(['de', 'en-GB', 'fr']);
const supported = supportedLocales.join(',');
const ro = new Intl.ListFormat('en-GB', {
style: 'short',
type: 'disjunction'
}).resolvedOptions();
const roTypeOk = JSON.stringify(ro).includes('"type":"disjunction"');
const tag = Intl.ListFormat.prototype[Symbol.toStringTag];
const defaultList = new Intl.ListFormat('en');
const ctor = defaultList.constructor === Intl.ListFormat;
const format = defaultList.format;
const fromGetter = format(list);
document.getElementById('result').textContent =
long + '|' + short + '|' + narrow + '|' + partsOk + '|' +
supported + '|' + ro.locale + ':' + ro.style + ':' + roTypeOk + '|' +
tag + '|' + ctor + '|' + fromGetter;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"Motorcycle, Bus and Car|Motorcycle, Bus or Car|Motorcycle Bus Car|true|de,en-GB|en-GB:short:true|Intl.ListFormat|true|Motorcycle, Bus, and Car",
)?;
Ok(())
}
#[test]
fn intl_locale_try_it_examples_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const korean = new Intl.Locale('ko', {
script: 'Kore',
region: 'KR',
hourCycle: 'h23',
calendar: 'gregory',
});
const japanese = new Intl.Locale('ja-Jpan-JP-u-ca-japanese-hc-h12');
document.getElementById('result').textContent =
korean.baseName + '|' + japanese.baseName + '|' +
korean.hourCycle + '|' + japanese.hourCycle;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "ko-Kore-KR|ja-Jpan-JP|h23|h12")?;
Ok(())
}
#[test]
fn intl_locale_properties_and_methods_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const locale = new Intl.Locale('en-Latn-US-u-ca-gregory-kf-upper-co-phonebk-kn-nu-latn');
const us = new Intl.Locale('en-US', { hourCycle: 'h12' });
const textInfo = new Intl.Locale('he-IL').getTextInfo();
const weekInfo = us.getWeekInfo();
const weekend = weekInfo.weekend;
const maximize = new Intl.Locale('zh').maximize();
const minimize = maximize.minimize();
const props =
locale.language + ':' + locale.script + ':' + locale.region + ':' +
locale.calendar + ':' + locale.caseFirst + ':' + locale.collation + ':' +
locale.numberingSystem + ':' + locale.numeric + ':' +
locale.hourCycle + ':' + locale.variants.length;
const calendars = us.getCalendars();
const collations = us.getCollations();
const hourCycles = us.getHourCycles();
const numberingSystems = us.getNumberingSystems();
const timeZones = us.getTimeZones();
const tag = Intl.Locale.prototype[Symbol.toStringTag];
const ctor = us.constructor === Intl.Locale;
const full = us.toString();
document.getElementById('result').textContent =
props + '|' + calendars.join(',') + '|' + collations.join(',') + '|' + hourCycles.join(',') + '|' +
numberingSystems.join(',') + '|' + textInfo.direction + '|' + timeZones.join(',') + '|' +
weekInfo.firstDay + ':' + weekInfo.minimalDays + ':' + weekend.join('/') + '|' +
maximize.baseName + ':' + minimize.baseName + '|' + tag + '|' + ctor + '|' + full;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"en:Latn:US:gregory:upper:phonebk:latn:true:undefined:0|gregory|default,emoji|h12,h23|latn|rtl|America/New_York,America/Los_Angeles|7:1:6/7|zh-Hans-CN:zh|Intl.Locale|true|en-US-u-hc-h12",
)?;
Ok(())
}
#[test]
fn intl_locale_raw_getter_and_call_paths_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const locale = new Intl.Locale('en-US', { hourCycle: 'h12' });
const maximizeTarget = new Intl.Locale('zh');
const toStringFn = locale['toString'];
const getCalendarsFn = locale['getCalendars'];
const maximizeFn = maximizeTarget['maximize'];
document.getElementById('result').textContent = [
String(toStringFn === Intl.Locale.prototype.toString),
String(getCalendarsFn === Intl.Locale.prototype.getCalendars),
String(maximizeFn === Intl.Locale.prototype.maximize),
String(toStringFn.name === 'toString'),
String(toStringFn.length === 0),
String(Function.prototype.toString.call(toStringFn) === toStringFn.toString()),
toStringFn.call(locale),
getCalendarsFn.call(locale).join(','),
maximizeFn.call(maximizeTarget).baseName
].join('|');
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"true|true|true|true|true|true|en-US-u-hc-h12|gregory|zh-Hans-CN",
)?;
Ok(())
}
#[test]
fn intl_formatter_prototype_methods_and_bound_format_metadata_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));
const dtf = new Intl.DateTimeFormat('en-US', { timeZone: 'UTC' });
const nf = new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' });
const list = new Intl.ListFormat('en', { style: 'short', type: 'conjunction' });
const duration = new Intl.DurationFormat('en', { style: 'short' });
const dtfToParts = dtf['formatToParts'];
const dtfRange = dtf['formatRange'];
const nfResolved = nf['resolvedOptions'];
const listToParts = list['formatToParts'];
const durationToParts = duration['formatToParts'];
const dtfPartsDirect = dtf['formatToParts'](date);
const dtfPartsViaCall = dtfToParts.call(dtf, date);
const dtfPartsJson = JSON.stringify(dtfPartsViaCall);
const dtfPartsOk =
JSON.stringify(dtfPartsDirect) === dtfPartsJson &&
dtfPartsJson.includes('"type":"month"') &&
dtfPartsJson.includes('"type":"day"') &&
dtfPartsJson.includes('"type":"year"');
const nfRangePartsJson = JSON.stringify(nf['formatRangeToParts'](1, 2));
const listPartsJson = JSON.stringify(list['formatToParts'](['Motorcycle', 'Bus', 'Car']));
const durationParts = duration['formatToParts']({ hours: 1, minutes: 2, seconds: 3 });
const durationPartsOk = durationParts.length > 0;
const options = nfResolved.call(nf);
document.getElementById('result').textContent = [
String(dtfToParts === Intl.DateTimeFormat.prototype.formatToParts),
String(dtfRange === Intl.DateTimeFormat.prototype.formatRange),
String(nfResolved === Intl.NumberFormat.prototype.resolvedOptions),
String(listToParts === Intl.ListFormat.prototype.formatToParts),
String(durationToParts === Intl.DurationFormat.prototype.formatToParts),
String(dtfPartsOk),
String(dtfRange.call(dtf, date, date).includes('2012')),
options.style + ':' + options.currency,
String(nfRangePartsJson.includes('"source":"startRange"') && nfRangePartsJson.includes('"source":"endRange"')),
String(listPartsJson.includes('"type":"element"') && listPartsJson.includes('"type":"literal"')),
String(durationPartsOk),
String(dtf.format.name === 'format'),
String(nf.format.name === 'format'),
String(Function.prototype.toString.call(dtfToParts) === dtfToParts.toString()),
String(Function.prototype.toString.call(nf.format) === nf.format.toString())
].join('|');
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"true|true|true|true|true|true|true|currency:EUR|true|true|true|true|true|true|true",
)?;
Ok(())
}
#[test]
fn intl_bound_format_getter_accessor_identity_and_receiver_parity_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const dtf = new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
timeZone: 'UTC'
});
const nf = new Intl.NumberFormat('en-US');
const date = new Date(Date.UTC(2020, 11, 20, 3, 0, 0));
const dtfFormat1 = dtf.format;
const dtfFormat2 = dtf['format'];
const nfFormat1 = nf.format;
const nfFormat2 = nf['format'];
let dtfErr = '';
try {
({ __proto__: Intl.DateTimeFormat.prototype }).format;
} catch (e) {
dtfErr = String(e);
}
let nfErr = '';
try {
({ __proto__: Intl.NumberFormat.prototype })['format'];
} catch (e) {
nfErr = String(e);
}
document.getElementById('result').textContent = [
String(dtfFormat1 === dtfFormat2 && dtfFormat1 === dtf.format),
String(nfFormat1 === nfFormat2 && nfFormat1 === nf.format),
String(!Object.prototype.hasOwnProperty.call(dtf, 'format')),
String(!Object.prototype.hasOwnProperty.call(nf, 'format')),
dtfFormat1(date),
nfFormat1(1234.5),
dtfErr,
nfErr,
String(dtfFormat1.name === 'format' && dtfFormat1.length === 1),
String(Function.prototype.toString.call(nfFormat1) === nfFormat1.toString())
].join('|');
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"true|true|true|true|12/20/2020|1,234.5|Intl.DateTimeFormat method requires an Intl.DateTimeFormat instance|Intl.NumberFormat method requires an Intl.NumberFormat instance|true|true",
)?;
Ok(())
}
#[test]
fn intl_date_time_format_accessor_assignment_noop_and_enumeration_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const dtf = new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
timeZone: 'UTC'
});
const nativeDtf = dtf.format;
let dtfBeforeForIn = '';
for (const key in dtf) dtfBeforeForIn += key;
dtf.format = (value) => 'dtf:' + value.getUTCFullYear();
const dtfAfterAssign = dtf.format;
let dtfAfterForIn = '';
for (const key in dtf) dtfAfterForIn += key;
delete dtf.format;
let dtfDeleteForIn = '';
for (const key in dtf) dtfDeleteForIn += key;
document.getElementById('result').textContent = [
String('format' in dtf),
String(!Object.prototype.hasOwnProperty.call(dtf, 'format')),
String(Object.keys(dtf).length === 0),
String(Object.keys({ ...dtf }).length === 0),
String(dtfBeforeForIn === ''),
String(dtfAfterAssign === nativeDtf),
String(dtf.format === nativeDtf),
dtf.format(new Date(Date.UTC(2020, 11, 20, 3, 0, 0))),
String(delete dtf.format),
String(dtf.format === nativeDtf),
dtf.format(new Date(Date.UTC(2020, 11, 20, 3, 0, 0))),
String(!Object.prototype.hasOwnProperty.call(dtf, 'format')),
String(Object.keys(dtf).length === 0),
String(Object.keys({ ...dtf }).length === 0),
String(dtfAfterForIn === ''),
dtfDeleteForIn,
JSON.stringify(dtf)
].join('|');
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"true|true|true|true|true|true|true|12/20/2020|true|true|12/20/2020|true|true|true|true||{}",
)?;
Ok(())
}
#[test]
fn intl_number_format_accessor_assignment_noop_and_enumeration_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const nf = new Intl.NumberFormat('en-US');
const nativeNf = nf.format;
let nfBeforeForIn = '';
for (const key in nf) nfBeforeForIn += key;
nf.format = (...args) => 'nf:' + args.length + ':' + args[0];
const nfAfterAssign = nf.format;
let nfAfterForIn = '';
for (const key in nf) nfAfterForIn += key;
delete nf.format;
let nfDeleteForIn = '';
for (const key in nf) nfDeleteForIn += key;
document.getElementById('result').textContent = [
String('format' in nf),
String(!Object.prototype.hasOwnProperty.call(nf, 'format')),
String(Object.keys(nf).length === 0),
String(Object.keys({ ...nf }).length === 0),
String(nfBeforeForIn === ''),
String(nfAfterAssign === nativeNf),
String(nf['format'] === nf.format),
String(nf.format === nativeNf),
nf.format(12),
nf['format'](12),
String(delete nf.format),
String(nf.format === nativeNf),
nf.format(1234.5),
String(!Object.prototype.hasOwnProperty.call(nf, 'format')),
String(Object.keys(nf).length === 0),
String(Object.keys({ ...nf }).length === 0),
String(nfAfterForIn === ''),
nfDeleteForIn,
JSON.stringify(nf)
].join('|');
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"true|true|true|true|true|true|true|true|12|12|true|true|1,234.5|true|true|true|true||{}",
)?;
Ok(())
}
#[test]
fn intl_number_format_descriptor_define_property_and_reflect_set_work() -> Result<()> {
let html = r#"
<p id='result'></p>
<script>
const nf = new Intl.NumberFormat('en-US');
window.nf = nf;
const nativeFormat = nf.format;
const protoDesc1 =
Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, 'format');
const protoDesc2 =
window.Object.getOwnPropertyDescriptor(window.Intl.NumberFormat.prototype, 'format');
const ownBefore = Object.getOwnPropertyDescriptor(window.nf, 'format');
const reflectBefore = Reflect.set(window.nf, 'format', (value) => 'bad:' + value);
const afterReflectBefore = window.nf.format;
const override1 = (value) => 'own:' + value;
const override2 = (value) => 'set:' + value;
const defined = Object.defineProperty(window.nf, 'format', {
value: override1,
enumerable: true
});
const ownAfterDefine = Object.getOwnPropertyDescriptor(window.nf, 'format');
let keysAfterDefine = '';
for (const key in window.nf) keysAfterDefine += key;
const reflectAfter = Reflect.set(window.nf, 'format', override2);
const ownAfterReflect = Object.getOwnPropertyDescriptor(window.nf, 'format');
delete window.nf.format;
const ownAfterDelete = Object.getOwnPropertyDescriptor(window.nf, 'format');
let keysAfterDelete = '';
for (const key in window.nf) keysAfterDelete += key;
document.getElementById('result').textContent = [
String(ownBefore === undefined),
String(protoDesc1.get === protoDesc2.get),
String(protoDesc1.set === undefined),
String(protoDesc1.enumerable === false && protoDesc1.configurable === true),
Object.keys(protoDesc1).join(','),
String(reflectBefore === false),
String(afterReflectBefore === nativeFormat),
String(defined === window.nf),
keysAfterDefine,
String(
ownAfterDefine.value === override1 &&
ownAfterDefine.enumerable === true &&
ownAfterDefine.writable === false &&
ownAfterDefine.configurable === false
),
String(reflectAfter === false),
String(
ownAfterReflect.value === override1 &&
ownAfterReflect.enumerable === true &&
ownAfterReflect.writable === false &&
ownAfterReflect.configurable === false
),
String(delete window.nf.format === false),
String(ownAfterDelete.value === override1),
keysAfterDelete,
window.nf.format(1234.5)
].join('|');
</script>
"#;
let h = Harness::from_html(html)?;
h.assert_text(
"#result",
"true|true|true|true|get,set,enumerable,configurable|true|true|true|format|true|true|true|true|true|format|own:1234.5",
)?;
Ok(())
}
#[test]
fn intl_number_format_main_realm_define_property_override_lookup_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const nf = new Intl.NumberFormat('en-US');
window.nf = nf;
const nativeFormat = nf.format;
const override1 = (value) => 'own:' + value;
const override2 = (value) => 'set:' + value;
Object.defineProperty(nf, 'format', {
value: override1,
enumerable: true
});
const ownCall = nf.format(12);
const pathCall = window.nf.format(12);
const bracketCall = nf['format'](12);
const extracted = nf.format;
const extractedCall = extracted(12);
const reflectAfter = Reflect.set(window.nf, 'format', override2);
const reflectCall = nf.format(13);
const reflectPathCall = window.nf['format'](13);
delete window.nf.format;
document.getElementById('result').textContent = [
String(ownCall === 'own:12'),
String(pathCall === 'own:12'),
String(bracketCall === 'own:12'),
String(extracted === override1),
String(extractedCall === 'own:12'),
String(reflectAfter === false),
String(reflectCall === 'own:13'),
String(reflectPathCall === 'own:13'),
String(delete window.nf.format === false),
String(nf.format !== nativeFormat),
nf.format(1234.5)
].join('|');
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"true|true|true|true|true|true|true|true|true|true|own:1234.5",
)?;
Ok(())
}
#[test]
fn object_and_reflect_alias_surface_work_in_main_realm() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const nf = new Intl.NumberFormat('en-US');
const symbol = Symbol('s');
const obj = { a: 1 };
obj[symbol] = 7;
const getDesc = window.Object['getOwnPropertyDescriptor'];
const define = Object.defineProperty;
const keys = window.Object.keys;
const values = Object['values'];
const entries = window.Object.entries;
const hasOwn = Object.hasOwn;
const getSymbols = Object.getOwnPropertySymbols;
const getPrototypeOf = window.Object.getPrototypeOf;
const freeze = Object.freeze;
const set = window.Reflect['set'];
define(nf, 'format', { value: (value) => 'alias:' + value, enumerable: true });
define(obj, 'b', { value: 2, enumerable: true });
const reflectResult = set(obj, 'c', 3);
const formatDesc = getDesc(nf, 'format');
const symbolKeys = getSymbols(obj);
document.getElementById('result').textContent = [
String(window.Object.getOwnPropertyDescriptor === Object.getOwnPropertyDescriptor),
String(window.Reflect.set === Reflect.set),
String(Object.getOwnPropertyDescriptor.name === 'getOwnPropertyDescriptor'),
String(Object.defineProperty.length === 3),
String(Reflect.set.length === 3),
String(formatDesc.value(12) === 'alias:12'),
nf.format(12),
keys(obj).join(','),
values(obj).join(','),
entries(obj).map((pair) => pair.join('=')).join(','),
String(hasOwn(obj, 'b') && hasOwn(obj, 'c') && !hasOwn(obj, 'z')),
String(symbolKeys.length === 1 && symbolKeys[0] === symbol),
String(getPrototypeOf(obj) === Object.prototype),
String(freeze(obj) === obj),
String(reflectResult === true),
String(Function.prototype.toString.call(Object.getOwnPropertyDescriptor) === Object.getOwnPropertyDescriptor.toString()),
String(Object.keys(Reflect).length === 0)
].join('|');
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"true|true|true|true|true|true|alias:12|a,b,c|1,2,3|a=1,b=2,c=3|true|true|true|true|true|true|true",
)?;
Ok(())
}
#[test]
fn intl_plural_rules_locales_and_options_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const enCardinalRules = new Intl.PluralRules('en-US');
const arCardinalRules = new Intl.PluralRules('ar-EG');
const enOrdinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });
document.getElementById('result').textContent =
enCardinalRules.select(0) + ':' + enCardinalRules.select(1) + ':' +
enCardinalRules.select(2) + ':' + enCardinalRules.select(3) + '|' +
arCardinalRules.select(0) + ':' + arCardinalRules.select(1) + ':' +
arCardinalRules.select(2) + ':' + arCardinalRules.select(6) + ':' +
arCardinalRules.select(18) + '|' +
enOrdinalRules.select(0) + ':' + enOrdinalRules.select(1) + ':' +
enOrdinalRules.select(2) + ':' + enOrdinalRules.select(3) + ':' +
enOrdinalRules.select(4) + ':' + enOrdinalRules.select(21);
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"other:one:other:other|zero:one:two:few:many|other:one:two:few:other:one",
)?;
Ok(())
}
#[test]
fn intl_plural_rules_methods_and_static_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const enOrdinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });
const supported = Intl.PluralRules.supportedLocalesOf(['ar-EG', 'en-US', 'de']);
const ro = enOrdinalRules.resolvedOptions();
const categories = ro.pluralCategories;
const categoriesText = categories.join(',');
const range =
enOrdinalRules.selectRange(1, 1) + ':' +
enOrdinalRules.selectRange(1, 2) + ':' +
new Intl.PluralRules('ar-EG').selectRange(0, 0);
const suffixes = { one: 'st', two: 'nd', few: 'rd', other: 'th' };
const formatOrdinals = (n) => {
const rule = enOrdinalRules.select(n);
return n + suffixes[rule];
};
const tag = Intl.PluralRules.prototype[Symbol.toStringTag];
const ctor = enOrdinalRules.constructor === Intl.PluralRules;
document.getElementById('result').textContent =
supported.join(',') + '|' +
ro.locale + ':' + ro['type'] + ':' + categoriesText + '|' +
range + '|' +
formatOrdinals(0) + ',' + formatOrdinals(1) + ',' + formatOrdinals(2) + ',' +
formatOrdinals(3) + ',' + formatOrdinals(4) + ',' + formatOrdinals(11) + ',' +
formatOrdinals(21) + ',' + formatOrdinals(42) + ',' + formatOrdinals(103) + '|' +
tag + '|' + ctor;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"ar-EG,en-US|en-US:ordinal:one,two,few,other|one:other:zero|0th,1st,2nd,3rd,4th,11th,21st,42nd,103rd|Intl.PluralRules|true",
)?;
Ok(())
}
#[test]
fn intl_relative_time_format_try_it_examples_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const rtf1 = new Intl.RelativeTimeFormat('en', { style: 'short' });
const qtrs = rtf1.format(3, 'quarter');
const ago = rtf1.format(-1, 'day');
const secs = rtf1.format(10, 'seconds');
const rtf2 = new Intl.RelativeTimeFormat('es', { numeric: 'auto' });
const auto = rtf2.format(2, 'day');
document.getElementById('result').textContent =
qtrs + '|' + ago + '|' + secs + '|' + auto;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "in 3 qtrs.|1 day ago|in 10 sec.|pasado maƱana")?;
Ok(())
}
#[test]
fn intl_relative_time_format_methods_and_options_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const rtf = new Intl.RelativeTimeFormat('en', {
localeMatcher: 'best fit',
numeric: 'auto',
style: 'long',
});
const text = rtf.format(-1, 'day');
const partsAuto = rtf.formatToParts(-1, 'day');
const parts = rtf.formatToParts(100, 'day');
const partsOk =
JSON.stringify(partsAuto).includes('"value":"yesterday"') &&
JSON.stringify(parts).includes('"type":"integer"') &&
JSON.stringify(parts).includes('"unit":"day"') &&
JSON.stringify(parts).includes('"value":"in "') &&
JSON.stringify(parts).includes('"value":"100"') &&
JSON.stringify(parts).includes('"value":" days"');
const supportedLocales = Intl.RelativeTimeFormat.supportedLocalesOf(['es', 'en', 'de']);
const supported = supportedLocales.join(',');
const ro = rtf.resolvedOptions();
const tag = Intl.RelativeTimeFormat.prototype[Symbol.toStringTag];
const ctor = rtf.constructor === Intl.RelativeTimeFormat;
document.getElementById('result').textContent =
text + '|' + partsOk + '|' + supported + '|' +
ro.locale + ':' + ro.style + ':' + ro.numeric + ':' + ro.numberingSystem + ':' + ro.localeMatcher + '|' +
tag + '|' + ctor;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"yesterday|true|es,en|en:long:auto:latn:best fit|Intl.RelativeTimeFormat|true",
)?;
Ok(())
}
#[test]
fn intl_relative_time_format_numeric_auto_day_literals_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const yesterday = rtf.format(-1, 'day');
const today = rtf.format(0, 'day');
const tomorrow = rtf.format(1, 'day');
document.getElementById('result').textContent =
yesterday + '|' + today + '|' + tomorrow;
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "yesterday|today|tomorrow")?;
Ok(())
}
#[test]
fn intl_relative_time_format_to_parts_try_it_example_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const parts = rtf.formatToParts(10, 'seconds');
const fromParts = parts.map((part) => part.value).join('');
const formatted = rtf.format(10, 'seconds');
const dayParts = rtf.formatToParts(-1, 'day');
const dayLiteralOnly =
dayParts.length === 1 &&
dayParts[0].type === 'literal' &&
dayParts[0].value === 'yesterday';
const shapeOk =
parts.length === 3 &&
parts[0].type === 'literal' &&
parts[0].value === 'in ' &&
parts[1].type === 'integer' &&
parts[1].value === '10' &&
parts[1].unit === 'second' &&
parts[2].type === 'literal' &&
parts[2].value === ' seconds';
document.getElementById('result').textContent = [
parts[0].value,
parts[1].value,
parts[2].value,
fromParts,
String(fromParts === formatted),
String(shapeOk),
String(dayLiteralOnly),
].join('|');
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text("#result", "in |10| seconds|in 10 seconds|true|true|true")?;
Ok(())
}
#[test]
fn intl_relative_time_format_resolved_options_examples_work() -> Result<()> {
let html = r#"
<button id='btn'>run</button>
<p id='result'></p>
<script>
document.getElementById('btn').addEventListener('click', () => {
const rtf1 = new Intl.RelativeTimeFormat('en', { style: 'narrow' });
const options1 = rtf1.resolvedOptions();
const rtf2 = new Intl.RelativeTimeFormat('es', { numeric: 'auto' });
const options2 = rtf2.resolvedOptions();
const rtf3 = new Intl.RelativeTimeFormat('en-u-nu-arab');
const options3 = rtf3.resolvedOptions();
const rtf4 = new Intl.RelativeTimeFormat('en', { numberingSystem: 'arab' });
const options4 = rtf4.resolvedOptions();
document.getElementById('result').textContent = [
`${options1.locale}, ${options1.style}, ${options1.numeric}, ${options1.numberingSystem}`,
`${options2.locale}, ${options2.style}, ${options2.numeric}, ${options2.numberingSystem}`,
options3.numberingSystem,
options4.numberingSystem,
].join('|');
});
</script>
"#;
let mut h = Harness::from_html(html)?;
h.click("#btn")?;
h.assert_text(
"#result",
"en, narrow, always, latn|es, long, auto, latn|arab|arab",
)?;
Ok(())
}