-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
added Selection::add_selection
and other add methods
#31
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -286,6 +286,107 @@ | |||||||||||||||||||||||||||||||||||
.collect(); | ||||||||||||||||||||||||||||||||||||
Selection { nodes } | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
/// Adds nodes that match the given CSS selector to the current selection. | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// # Panics | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// If matcher contains invalid CSS selector it panics. | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// # Arguments | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// * `sel` - The CSS selector to match against. | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// # Returns | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// The new `Selection` containing the original nodes and the new nodes. | ||||||||||||||||||||||||||||||||||||
pub fn add(&self, sel: &str) -> Selection<'a> { | ||||||||||||||||||||||||||||||||||||
if self.is_empty() { | ||||||||||||||||||||||||||||||||||||
return self.clone(); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
let matcher = Matcher::new(sel).expect("Invalid CSS selector"); | ||||||||||||||||||||||||||||||||||||
self.add_matcher(&matcher) | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
/// Adds nodes that match the given CSS selector to the current selection. | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// If matcher contains invalid CSS selector it returns `None`. | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// # Arguments | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// * `sel` - The CSS selector to match against. | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// # Returns | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// The new `Selection` containing the original nodes and the new nodes. | ||||||||||||||||||||||||||||||||||||
pub fn try_add(&self, sel: &str) -> Option<Selection> { | ||||||||||||||||||||||||||||||||||||
if self.is_empty() { | ||||||||||||||||||||||||||||||||||||
return Some(self.clone()); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
Matcher::new(sel).ok().map(|m| self.add_matcher(&m)) | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
/// Adds nodes that match the given matcher to the current selection. | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// # Arguments | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// * `matcher` - The matcher to match against. | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// # Returns | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// The new `Selection` containing the original nodes and the new nodes. | ||||||||||||||||||||||||||||||||||||
pub fn add_matcher(&self, matcher: &Matcher) -> Selection<'a> { | ||||||||||||||||||||||||||||||||||||
if self.is_empty() { | ||||||||||||||||||||||||||||||||||||
return self.clone(); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
let root = self.nodes().first().unwrap().tree.root(); | ||||||||||||||||||||||||||||||||||||
let other_nodes: Vec<NodeRef> = | ||||||||||||||||||||||||||||||||||||
Matches::from_one(root, matcher, MatchScope::IncludeNode).collect(); | ||||||||||||||||||||||||||||||||||||
let new_nodes = self.merge_nodes(other_nodes); | ||||||||||||||||||||||||||||||||||||
Selection { nodes: new_nodes } | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
/// Adds a selection to the current selection. | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// Behaves like `Union` for sets. | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// # Arguments | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// * `other` - The selection to add to the current selection. | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// # Returns | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// A new `Selection` object containing the combined elements. | ||||||||||||||||||||||||||||||||||||
pub fn add_selection(&self, other: &'a Selection) -> Selection<'a> { | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
if self.is_empty() { | ||||||||||||||||||||||||||||||||||||
return other.clone(); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
if other.is_empty() { | ||||||||||||||||||||||||||||||||||||
return self.clone(); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
self.ensure_same_tree(other); | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
let other_nodes = other.nodes(); | ||||||||||||||||||||||||||||||||||||
let new_nodes = self.merge_nodes(other_nodes.to_vec()); | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
Selection { nodes: new_nodes } | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
fn merge_nodes(&self, other_nodes: Vec<NodeRef<'a>>) -> Vec<NodeRef<'a>> { | ||||||||||||||||||||||||||||||||||||
let m: Vec<usize> = self.nodes().iter().map(|node| node.id.value).collect(); | ||||||||||||||||||||||||||||||||||||
let add_nodes: Vec<NodeRef> = other_nodes | ||||||||||||||||||||||||||||||||||||
.iter() | ||||||||||||||||||||||||||||||||||||
.filter(|&node| !m.contains(&node.id.value)) | ||||||||||||||||||||||||||||||||||||
.cloned() | ||||||||||||||||||||||||||||||||||||
.collect(); | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
let mut new_nodes = self.nodes().to_vec(); | ||||||||||||||||||||||||||||||||||||
new_nodes.extend(add_nodes); | ||||||||||||||||||||||||||||||||||||
new_nodes | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
//manipulating methods | ||||||||||||||||||||||||||||||||||||
|
@@ -624,6 +725,21 @@ | |||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
impl<'a> Selection<'a> { | ||||||||||||||||||||||||||||||||||||
/// Ensures that the two selections are from the same tree. | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// # Panics | ||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||
/// Panics if the selections are from different trees or if they are empty. | ||||||||||||||||||||||||||||||||||||
fn ensure_same_tree(&self, other: &Selection) { | ||||||||||||||||||||||||||||||||||||
let tree = self.nodes().first().unwrap().tree; | ||||||||||||||||||||||||||||||||||||
let other_tree = other.nodes().first().unwrap().tree; | ||||||||||||||||||||||||||||||||||||
if !std::ptr::eq(tree, other_tree) { | ||||||||||||||||||||||||||||||||||||
panic!("Selections must be from the same tree"); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
Comment on lines
+734
to
+740
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential panic when selections are empty in The method Apply this diff to handle empty selections: fn ensure_same_tree(&self, other: &Selection) {
+ if self.is_empty() || other.is_empty() {
+ panic!("Selections must not be empty");
+ }
let tree = self.nodes().first().unwrap().tree;
let other_tree = other.nodes().first().unwrap().tree;
if !std::ptr::eq(tree, other_tree) {
panic!("Selections must be from the same tree");
}
} 📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Check: codecov/patch[warning] 733-733: src/selection.rs#L733 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I pushed a fix in commit 88dfbd7, please review it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @niklak, thank you for fixing the issue. I have reviewed the changes in commit 88dfbd7, and the added checks in If you found this review helpful, would you consider giving us a shout-out on X? |
||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
/// Iterator over a collection of matched elements. | ||||||||||||||||||||||||||||||||||||
pub struct Selections<I> { | ||||||||||||||||||||||||||||||||||||
iter: IntoIter<I>, | ||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -386,3 +386,49 @@ fn test_ancestors_selection_with_limit() { | |
assert!(ancestor_sel.is("#parent")); | ||
assert!(!ancestor_sel.is("#great-ancestor")); | ||
} | ||
|
||
#[cfg_attr(not(target_arch = "wasm32"), test)] | ||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] | ||
fn test_selection_add_selection() { | ||
let doc: Document = ANCESTORS_CONTENTS.into(); | ||
|
||
let first_sel = doc.select("#first-child"); | ||
assert_eq!(first_sel.length(), 1); | ||
let second_sel = doc.select("#second-child"); | ||
assert_eq!(second_sel.length(), 1); | ||
let children_sel = first_sel.add_selection(&second_sel); | ||
assert_eq!(children_sel.length(), 2); | ||
} | ||
|
||
#[cfg_attr(not(target_arch = "wasm32"), test)] | ||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] | ||
fn test_selection_add() { | ||
let doc: Document = ANCESTORS_CONTENTS.into(); | ||
|
||
let children_sel = doc.select("#first-child").add("#second-child"); | ||
assert_eq!(children_sel.length(), 2); | ||
} | ||
|
||
#[cfg_attr(not(target_arch = "wasm32"), test)] | ||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] | ||
#[should_panic] | ||
fn test_selection_add_panic() { | ||
let doc: Document = ANCESTORS_CONTENTS.into(); | ||
|
||
doc.select("#first-child").add(":;'"); | ||
} | ||
Comment on lines
+412
to
+419
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve panic testing specificity. Instead of using a broad -#[should_panic]
+#[should_panic(expected = "failed to parse selector")]
fn test_selection_add_panic() {
|
||
|
||
#[cfg_attr(not(target_arch = "wasm32"), test)] | ||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] | ||
fn test_selection_try_add() { | ||
let doc: Document = ANCESTORS_CONTENTS.into(); | ||
|
||
let first_child_sel = doc.select("#first-child"); | ||
assert_eq!(first_child_sel.length(), 1); | ||
|
||
let children_sel = first_child_sel.try_add(":;'"); | ||
assert!(children_sel.is_none()); | ||
|
||
let children_sel = first_child_sel.try_add("#second-child"); | ||
assert_eq!(children_sel.unwrap().length(), 2); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Handle empty
self
inadd_matcher
to include matches from the entire documentCurrently,
add_matcher
returns an empty selection whenself
is empty, which might not be the expected behavior. When the current selection is empty, it may be more intuitive to perform the addition starting from the document root. Consider modifyingadd_matcher
to handle the emptyself
case by initiating the search from the document root.Proposed change:
🧰 Tools
🪛 GitHub Check: codecov/patch
[warning] 340-340: src/selection.rs#L340
Added line #L340 was not covered by tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@niklak, understood.
✏️ Learnings added