DB & Dev Tools · Tools and machines: washes paint, strict hex, tested levels
Text fields paint each run's wash before selections and glyphs, so a Highlight's background shows: the regex tester's matches, a mention's link and markdown's code. A tile's value gives way and wraps in a narrow box. Hex and percent escapes take two hex digits and no sign. Tests cover each WCAG level at its edge, a clean exit's quiet badge, a stopped container's line without load, and a reading past its limit.
This commit is contained in:
1 parent
f7013b17ef
commit
8b6bf80adf
7 files changed
+109
-36
No files matched your search
@@ -393,3 +393,4 @@ Per-component lines live in `tasks/`. Tags there:
|
||||
| T33c | 1 | FAIL | the socket log stayed at its top; a revealed secret stayed open across environments; the collection drew drags it never moved; the builder's refill, Send's rest and Bearer, the indented body, the eye and a field's pick path untested |
|
||||
| T33c | 2 | FAIL | where an Inside drop lands in a folder that holds something went untested |
|
||||
| T33c | 3 | PASS | |
|
||||
| T33d | 1 | FAIL | text fields never painted a highlight's wash; a tile's value ran past 280px; a stray + read as a hex digit; the WCAG thresholds, an exit's badge, a stopped row's load and the meter's clamp untested |
|
||||
@@ -212,4 +212,15 @@ mod tests {
|
||||
);
|
||||
assert!(contrast(blended, white) < contrast(gpui::hsla(0.0, 0.0, 0.0, 1.0), white));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_level_starts_at_its_ratio() {
|
||||
let met = |ratio: f32| grades(ratio).map(|(_, met)| met);
|
||||
assert_eq!(met(2.9), [false, false, false, false]);
|
||||
assert_eq!(met(3.1), [false, false, true, false]);
|
||||
assert_eq!(met(4.4), [false, false, true, false]);
|
||||
assert_eq!(met(4.6), [true, false, true, true]);
|
||||
assert_eq!(met(6.9), [true, false, true, true]);
|
||||
assert_eq!(met(7.1), [true, true, true, true]);
|
||||
}
|
||||
}
|
||||
+15
-5
@@ -81,11 +81,17 @@ pub fn unhex(text: &str) -> Result<Vec<u8>, String> {
|
||||
.chunks(2)
|
||||
.map(|pair| {
|
||||
let digits: String = pair.iter().collect();
|
||||
u8::from_str_radix(&digits, 16).map_err(|_| format!("{digits:?} is not hex"))
|
||||
byte_of(&digits).ok_or_else(|| format!("{digits:?} is not hex"))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The byte two hex digits spell; a sign or anything else is none.
|
||||
fn byte_of(digits: &str) -> Option<u8> {
|
||||
(digits.len() == 2 && digits.chars().all(|ch| ch.is_ascii_hexdigit()))
|
||||
.then(|| u8::from_str_radix(digits, 16).expect("two hex digits read"))
|
||||
}
|
||||
|
||||
/// Percent-encoding undone: `%XX` becomes its byte and `+` a space.
|
||||
pub fn unpercent(text: &str) -> Result<Vec<u8>, String> {
|
||||
let bytes = text.as_bytes();
|
||||
@@ -96,10 +102,7 @@ pub fn unpercent(text: &str) -> Result<Vec<u8>, String> {
|
||||
let digits = text
|
||||
.get(ix + 1..ix + 3)
|
||||
.ok_or("a % needs two hex digits after it")?;
|
||||
out.push(
|
||||
u8::from_str_radix(digits, 16)
|
||||
.map_err(|_| format!("%{digits} is not an escape"))?,
|
||||
);
|
||||
out.push(byte_of(digits).ok_or_else(|| format!("%{digits} is not an escape"))?);
|
||||
ix += 3;
|
||||
}
|
||||
b'+' => {
|
||||
@@ -292,6 +295,13 @@ mod tests {
|
||||
assert!(unbase64("TQ==TWFu").is_err(), "pads end the text");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_sign_is_no_hex_digit() {
|
||||
assert!(unhex("+f").is_err());
|
||||
assert!(unpercent("%+f").is_err());
|
||||
assert_eq!(unhex("0f").expect("hex"), [15]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_and_url_round_trip_and_name_their_faults() {
|
||||
assert_eq!(hex(b"hi"), "6869");
|
||||
|
||||
+46
-16
@@ -52,6 +52,23 @@ pub struct Container {
|
||||
|
||||
type OnKey = Rc<dyn Fn(&SharedString, &mut Window, &mut App)>;
|
||||
|
||||
/// A container's line under its name: its image, its ports, and its load while it runs.
|
||||
fn described(container: &Container) -> String {
|
||||
let ports = match container.ports.is_empty() {
|
||||
true => String::new(),
|
||||
false => format!(" · {}", container.ports.join(", ")),
|
||||
};
|
||||
let load = match container.state == ContainerState::Running {
|
||||
true => format!(
|
||||
" · {} · {}",
|
||||
format::percent(container.cpu as f64, 0, false),
|
||||
format::file_size(container.memory, true)
|
||||
),
|
||||
false => String::new(),
|
||||
};
|
||||
format!("{}{ports}{load}", container.image)
|
||||
}
|
||||
|
||||
/// Containers on a host, each with its image and ports, its load while it runs, and where it stands. Its buttons, each shown when the owner handles it, start or stop it, restart it and open its logs; Enter or a double press opens it.
|
||||
#[derive(IntoElement)]
|
||||
pub struct ContainerList {
|
||||
@@ -133,24 +150,10 @@ impl RenderOnce for ContainerList {
|
||||
)
|
||||
})
|
||||
};
|
||||
let load = if running {
|
||||
format!(
|
||||
" · {} · {}",
|
||||
format::percent(container.cpu as f64, 0, false),
|
||||
format::file_size(container.memory, true)
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let ports = if container.ports.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" · {}", container.ports.join(", "))
|
||||
};
|
||||
list.row(
|
||||
key.clone(),
|
||||
ListItem::new((id.clone(), format!("row-{key}")), container.name.clone())
|
||||
.description(format!("{}{ports}{load}", container.image))
|
||||
.description(described(container))
|
||||
.leading(Icon::new(IconName::Package).size(IconSize::Md).color(muted))
|
||||
.trailing(
|
||||
div()
|
||||
@@ -354,7 +357,34 @@ impl RenderOnce for PodList {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Pod, PodPhase, in_namespace, namespaces, running_for};
|
||||
use super::{
|
||||
Container, ContainerState, Pod, PodPhase, described, in_namespace, namespaces, running_for,
|
||||
};
|
||||
use crate::data_display::Tone;
|
||||
|
||||
#[test]
|
||||
fn a_clean_exit_rests_quiet_and_a_stopped_container_shows_no_load() {
|
||||
assert_eq!(
|
||||
ContainerState::Exited(0).badge(),
|
||||
(Tone::Neutral, "Exited".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
ContainerState::Exited(1).badge(),
|
||||
(Tone::Danger, "Exited 1".to_string())
|
||||
);
|
||||
let mut web = Container {
|
||||
key: "web".into(),
|
||||
name: "web".into(),
|
||||
image: "img".into(),
|
||||
state: ContainerState::Running,
|
||||
ports: vec!["80:80".into()],
|
||||
cpu: 0.25,
|
||||
memory: 1 << 20,
|
||||
};
|
||||
assert_eq!(described(&web), "img · 80:80 · 25% · 1.0 MiB");
|
||||
web.state = ContainerState::Exited(0);
|
||||
assert_eq!(described(&web), "img · 80:80");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pod_runs_from_its_start_and_a_clock_ahead_reads_as_now() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use gpui::{
|
||||
App, ElementId, FontWeight, IntoElement, ParentElement, RenderOnce, SharedString, Styled,
|
||||
Window, div,
|
||||
App, ElementId, FontWeight, InteractiveElement, IntoElement, ParentElement, RenderOnce,
|
||||
SharedString, Styled, Window, div,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -73,6 +73,7 @@ impl RenderOnce for ResourceMonitor {
|
||||
let latest = *reading.values.last().expect("a reading has values");
|
||||
let shown = reading_line(reading);
|
||||
div()
|
||||
.debug_selector(move || format!("monitor-tile-{ix}"))
|
||||
.flex_1()
|
||||
.min_w(theme.label_width())
|
||||
.flex()
|
||||
@@ -97,6 +98,8 @@ impl RenderOnce for ResourceMonitor {
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.text_size(theme.text_size(TextSize::Lg))
|
||||
.font_weight(FontWeight::SEMIBOLD)
|
||||
.child(shown),
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use gpui::{AnyElement, App, Entity, IntoElement, TestAppContext, Window};
|
||||
|
||||
use super::{Bench, bench, said, say, tab, tap};
|
||||
use crate::devtools::{Container, ContainerList, ContainerState, Encoder, RegexTester};
|
||||
use crate::devtools::{
|
||||
Container, ContainerList, ContainerState, Encoder, Reading, RegexTester, ResourceMonitor,
|
||||
};
|
||||
|
||||
fn list(_: &Bench, _: &mut Window, _: &mut App, owner: Entity<Bench>) -> AnyElement {
|
||||
let container = |key: &str, state| Container {
|
||||
@@ -76,3 +78,18 @@ fn decoding_base64_copies_the_text(cx: &mut TestAppContext) {
|
||||
.and_then(|item| item.text());
|
||||
assert_eq!(copied.as_deref(), Some("hi"));
|
||||
}
|
||||
|
||||
fn over(_: &Bench, _: &mut Window, _: &mut App, _: Entity<Bench>) -> AnyElement {
|
||||
ResourceMonitor::new(
|
||||
"monitor",
|
||||
[Reading::new("Queue", "jobs", [80.0, 120.0]).limit(100.0)],
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// A meter takes a share from 0 to 1, so a reading past its limit must fill it, not break it.
|
||||
#[gpui::test]
|
||||
fn a_reading_past_its_limit_fills_its_meter(cx: &mut TestAppContext) {
|
||||
let (_, cx) = bench(over, cx);
|
||||
assert!(cx.debug_bounds("monitor-tile-0").is_some(), "the tile drew");
|
||||
}
|
||||
+13
-12
@@ -283,21 +283,22 @@ impl Element for TextElement {
|
||||
);
|
||||
let line_height = window.line_height();
|
||||
window.with_content_mask(Some(ContentMask { bounds }), |window| {
|
||||
let mut origins = Vec::with_capacity(prepaint.lines.len());
|
||||
let mut top = Pixels::ZERO;
|
||||
for (_, line) in &prepaint.lines {
|
||||
origins.push(bounds.origin - scroll + point(Pixels::ZERO, top));
|
||||
top += line.size(line_height).height;
|
||||
}
|
||||
for ((_, line), origin) in prepaint.lines.iter().zip(&origins) {
|
||||
line.paint_background(*origin, line_height, TextAlign::Left, None, window, cx)
|
||||
.expect("text input wash paint failed");
|
||||
}
|
||||
for rect in prepaint.selections.drain(..) {
|
||||
window.paint_quad(fill(rect, prepaint.selection_color));
|
||||
}
|
||||
let mut top = Pixels::ZERO;
|
||||
for (_, line) in &prepaint.lines {
|
||||
line.paint(
|
||||
bounds.origin - scroll + point(Pixels::ZERO, top),
|
||||
line_height,
|
||||
TextAlign::Left,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.expect("text input paint failed");
|
||||
top += line.size(line_height).height;
|
||||
for ((_, line), origin) in prepaint.lines.iter().zip(&origins) {
|
||||
line.paint(*origin, line_height, TextAlign::Left, None, window, cx)
|
||||
.expect("text input paint failed");
|
||||
}
|
||||
if let Some(caret) = prepaint.caret.take() {
|
||||
window.paint_quad(fill(caret, prepaint.caret_color));
|
||||
|
||||
Reference in new issue
Block a user