Skip to content

RS-21803: checkImageTag style attribute test - #72

Merged
SurreyHughesDisplayr merged 4 commits into
masterfrom
RS-21803-checkImageTagstyleattribute
Aug 26, 2026
Merged

RS-21803: checkImageTag style attribute test#72
SurreyHughesDisplayr merged 4 commits into
masterfrom
RS-21803-checkImageTagstyleattribute

Conversation

@SurreyHughesDisplayr

@SurreyHughesDisplayr SurreyHughesDisplayr commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds unit test coverage for checkImageTag's handling of the style attribute and its surrounding link-cleaning paths (R/checkimagetag.R) — 7 test blocks in the existing test-checkimagetag.R.

  • Covers a style attribute before and after src, exercising the two different quote/bracket cleaning paths rather than the same one twice.
  • Covers double-quoted and unquoted src values, and src followed by trailing text.
  • Covers both syntax-error branches with one input each: an <img ...> that matches the tag regex but has no src=, and an input with no closing > that misses the tag regex first. The two warnings are byte-identical (each echoes the whole input), so no assertion on the message can tell them apart — separate inputs are what pins the branches.
  • Asserts the success path wraps the original text, not the matched img tag.
  • Each success block asserts the whole returned string in one expect_equal rather than several substring greps.

Also closes a pre-existing gap: the file's txt.dq case was single-quoted and identical to txt.sq, so double quotes were never actually tested. The style literal is now a single hoisted constant, replacing a dangling unused assignment.

Note on the src= regex

The last block pins current behaviour for a decoy input where an earlier attribute value contains src=. regexpr("src=(\S+)", ...) is unanchored, so alt='mysrc=http://x.invalid/' is matched in preference to the real src, and a valid image is silently removed with a user-visible invalid link which has been removed: http://x.invalid/ warning. The warning is anchored on the extracted link, so the assertion pins the decoy rather than merely a failed GET, and the decoy uses the RFC 2606 reserved .invalid TLD so it is inert regardless of what the network's DNS does.

This looks like a genuine low-severity defect rather than intended behaviour: nothing in the function or its callers suggests the loose match is deliberate, and a boundary requirement such as (?:^|\s)src= would be safe because the enclosing <img [^>]+> match guarantees whitespace before every attribute. Not fixed here — filed as RS-23603 (Bug, Severity D - Minor), which the test comment references. Separately, the trailing-whitespace sub() in the same function is unreachable, since (\S+) cannot capture whitespace — inert rather than wrong.

Network dependency

The success-path blocks perform a live HTTP GET of a real image URL — the pre-existing convention in this file, not introduced here. Rather than failing offline, every block that needs a link now guards on skipIfOffline(), which probes the exact links the block fetches (kImageLink, and kDropboxLink for the first block) once per link per run.

testthat::skip_if_offline() is deliberately not used: it calls skip_on_cran(), which would skip these blocks in any run where NOT_CRAN is unset, network or no. Verified by pointing both link constants at an unreachable host — 6 skips, 6 passes, 0 failures.

Jira: https://numbers.atlassian.net/browse/RS-21803

Stacked PR

Based on RS-21803-colwidthsandcustomcss (#71). Merge order: #66, #67, #68, #69, #70, #71, then this. This is the last of the seven RS-21803 plans; it is the only one touching checkimagetag.R rather than createcustomtable.R, so it shares no test file with the others.

Test plan

Rscript -e "devtools::load_all('.'); testthat::test_file('tests/testthat/test-checkimagetag.R')"

28 passing, 0 failures, 0 warnings with network up; 6 passing and 6 skipped without it.

🤖 Generated with Claude Code

SurreyHughesDisplayr and others added 2 commits August 21, 2026 14:47
Adds scenarios for style and width attributes preceding src, attribute
order independence, double-quoted and unquoted src values, a missing
src attribute, surrounding text preservation, and a decoy substring
"src=" match that is not silently treated as a valid link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace unfailable identical() check and network-dependent warning match
in the src= decoy block with pinned real values (result "" and warning
text including the captured "x"); pin echoed input in the missing-src
syntax-error message to distinguish it from the tag-regex-miss branch;
drop redundant duplicate expect_warning call; hoist repeated txt.style
literal to a file-level constant and remove dangling duplicate txt.dq
assignment; rename reused txt.dq in the double-quote block; document
network dependency of success-path blocks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@SurreyHughesDisplayr
SurreyHughesDisplayr requested a balanced review from Copilot August 24, 2026 06:54
…ions

Addresses the code review of PR #72:

- The five success-path blocks fetch a real image from
  wiki.q-researchsoftware.com. Without access to that host GET() errors,
  checkImageTag() warns and strips the tag, and the assertions failed rather
  than skipped. Each block now guards on a cached probe of the exact image it
  depends on. testthat's skip_if_offline() is deliberately not used: it calls
  skip_on_cran(), which would skip the blocks in any run where NOT_CRAN is
  unset, network or no.
- Anchor the decoy-src warning on the extracted link, so a regression in the
  quote stripping (which would produce "x'") can no longer satisfy it.
- Reference defect RS-23603 from the block that pins the unanchored src=
  match, per the convention used for the banding defects.
- txt.dq was byte-identical to txt.sq and asserted nothing new; make it
  genuinely double-quoted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@SurreyHughesDisplayr
SurreyHughesDisplayr changed the base branch from RS-21803-colwidthsandcustomcss to master August 26, 2026 05:45

@chschan chschan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the diff and ran it: 24 passed / 0 failed / 0 skipped here with network up, so the test-plan claim holds. DESCRIPTION correctly stays at 1.11.11 for a tests-only change, and the txt.dq fix is real — double quotes were genuinely never exercised before, and nchar == 84 still holds since both quote characters are one byte. No caller behaviour changes.

Approving with comments. Three worth doing before merge:

  1. The PR description is stale relative to the head commit. The "Network dependency" section says the blocks "fail offline. Documented in a comment at the top of the file" — but 3b8025a added skipIfOffline(), so the behaviour is now skip, not fail. Please update the body.
  2. The offline guard is incomplete and the header NOTE overstates it (inline at line 7).
  3. The decoy test's failure mode depends on DNS resolution (inline at line 117).

The rest are cleanups.


One minor note that can't be inlined (lines 43-47 fall between the two diff hunks, so GitHub won't take a comment there):

Unlike every other branch asserted in this file, the empty-link path never checks the return value - there's no res <- and no expect_equal. Worth pinning, because that branch returns via sub(imgtag, "", text) rather than the bare return("") the two syntax-error branches use, and the difference only shows up with surrounding text:

expect_warning(res <- checkImageTag("<img src=''>Some text"),
               "an image tag with an empty link")
expect_equal(res, "Some text")

Pre-existing and entirely optional for this PR.

Comment thread tests/testthat/test-checkimagetag.R Outdated
# order, quoting variants, surrounding text) fetch a real image from
# wiki.q-researchsoftware.com. Without network access to that host GET() errors,
# checkImageTag() warns and strips the tag, and those assertions would fail rather
# than skip - so each such block guards itself with skipIfOffline().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This overstates what's guarded. The test_that("checkImageTag") block below is also network-dependent and has no skipIfOffline() — line 57 fetches exactly kImageLink, and lines 36/40 fetch a Dropbox URL.

Simulating an offline run (stubbed failing GET()): the five new blocks skip cleanly, but that block produces 3 hard failures — nchar(res) 0 vs 84 twice, plus the txt.withattr equality. So the file still fails offline rather than skipping.

Either guard that block too, or narrow this wording to name the blocks it actually covers.

(The skip_if_offline() / skip_on_cran() rationale in the second paragraph is a good catch and worth keeping.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right — guarded that block rather than narrowing the wording (29bfde7). The probe now caches per link, skipIfOffline() takes the links a block needs, and the first block guards on c(kImageLink, kDropboxLink). Header NOTE rewritten to say what is actually covered, including that the empty-link assertions need no network but skip with the block they live in. Simulated offline (both constants pointed at an unreachable host): 6 skips, 6 passes, 0 failures — previously that block gave 3 hard failures.

Comment thread tests/testthat/test-checkimagetag.R Outdated
@@ -26,6 +56,64 @@ test_that("checkImageTag",
<img src=https://wiki.q-researchsoftware.com/images/c/cb/CokeZero.png width='45' height='100'></a>"
expect_error(res <- checkImageTag(txt.withattr), NA)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the unguarded one. It fetches the same kImageLink URL the probe already checks, so skipIfOffline() here is free and correct.

The Dropbox assertions at lines 36/40 need their own probe, or the header NOTE should say the file isn't fully offline-safe.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 29bfde7 — that block now calls skipIfOffline(c(kImageLink, kDropboxLink)), and the Dropbox URL is a kDropboxLink constant the probe checks alongside the wiki image, so neither needs its own wording caveat. Its two nchar(res) == 84 assertions are now full equality against paste0("<div>", txt, "</div>"), which both subsumes the byte count and stops the expected value drifting if the constant changes.

Comment thread tests/testthat/test-checkimagetag.R Outdated
expect_equal(res, paste0("<div>", kTxtStyle, "</div>"))
})

test_that("checkImageTag: src location is independent of attribute order",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name overstates what the function does — the decoy block at line 108 demonstrates it is not order-independent once an earlier attribute value contains src=.

Suggest "src is found when it is the first attribute".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to "checkImageTag: src is found when it is the first attribute" in 29bfde7, with a comment pointing at the decoy block as the counter-example.

Comment thread tests/testthat/test-checkimagetag.R Outdated
test_that("checkImageTag: src location is independent of attribute order",
{
skipIfOffline()
txt.order <- "<img src='https://wiki.q-researchsoftware.com/images/c/cb/CokeZero.png' style='margin:0 auto;' width='50'>"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

txt.order here (and txt.dq2 at line 79, txt.unquoted at line 87) hard-code the wiki URL as a literal, while the probe and kTxtStyle use kImageLink.

The guard is then correct only by coincidence of duplicated literals: change kImageLink (new host, moved image) and the probe reports "reachable" while these three blocks still GET the stale URL — turning the intended skip back into a hard failure, which is the exact failure mode this PR set out to remove.

paste0("<img src='", kImageLink, "' style='margin:0 auto;' width='50'>") etc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — all duplicated literals are gone in 29bfde7. txt.order, txt.dq2, txt.unquoted, txt.decoy and the txt.withattr fixture in the first block are now built with paste0(..., kImageLink, ...), so the probe and the requests can no longer diverge. The Dropbox URL got the same treatment via kDropboxLink.

Comment thread tests/testthat/test-checkimagetag.R Outdated
{
skipIfOffline()
txt.order <- "<img src='https://wiki.q-researchsoftware.com/images/c/cb/CokeZero.png' style='margin:0 auto;' width='50'>"
expect_error(res <- checkImageTag(txt.order), NA)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent absence-assertion across structurally identical blocks: line 64 uses expect_warning(..., NA), but this line (and 80, 88, 104) uses only expect_error(..., NA), which says nothing about warnings.

A regression that makes checkImageTag warn-and-strip is pinned as a clear failure in the first block, but surfaces in these as a testthat warning plus a downstream equality failure — which reads like a network problem rather than a behaviour change.

expect_warning(..., NA) uniformly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 29bfde7 — every success-path call now uses expect_warning(..., NA), including the two in the first block that previously only used expect_error(..., NA). An error still fails the test, so nothing is lost by dropping the error form.

{
txt.nosrc <- "<img style='margin:0 auto;' width='50'>"
expect_warning(res <- checkImageTag(txt.nosrc),
"syntax error which has been removed: <img style='margin:0 auto;' width='50'>")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says this pins the echoed input "so it is distinguishable from the tag-regex-miss branch that emits a byte-identical syntax error prefix". It doesn't — both branches in checkimagetag.R warn with "...syntax error which has been removed: ", text, i.e. the same text, so for this input the two messages are byte-identical:

"<img style=1"                            -> syntax error which has been removed: <img style=1
"<img style='margin:0 auto;' width='50'>" -> syntax error which has been removed: <img style='margin:0 auto;' width='50'>

It happens not to matter today, since this input does match <img [^>]+> and so can only reach the src= branch. But if the tag regex is later loosened or tightened so this input stops matching, the test keeps passing while silently covering the other branch.

Distinguishing them needs two inputs — one that fails the tag regex, one that fails only src= — not a pinned echo. Either add the second input or drop the claim from the description.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct — the echo distinguishes nothing, since both branches warn with the same text. Took the "add the second input" option in 29bfde7: the block is now "both syntax-error branches warn and return an empty string" and covers one input per branch — the existing <img style='margin:0 auto;' width='50'> matches <img [^>]+> and so can only reach the src= branch, and "<img style='margin:0 auto;' width='50'" (no closing >) misses the tag regex first. The comment states outright that no assertion on the warning can tell them apart. The stale claim is also removed from the PR description.

Comment thread tests/testthat/test-checkimagetag.R Outdated
# The warning is anchored on the extracted link so a regression in the quote
# stripping (which would give "x'") cannot satisfy it.
txt.decoy <- "<img alt='mysrc=x' src='https://wiki.q-researchsoftware.com/images/c/cb/CokeZero.png'>"
expect_warning(res <- checkImageTag(txt.decoy), "invalid link which has been removed: x$")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion depends on DNS luck. The extracted link is the bare string x, and the block only passes because httr::GET("x") fails to resolve it. On a network with a DNS search suffix that makes a single-label x resolvable, or behind a proxy/captive portal that answers arbitrary hosts with 200, checkImageTag takes the success path, emits no warning, and this fails with a misleading "no warning" message rather than a pinned behaviour change.

Use an RFC 2606 reserved TLD so it's inert regardless of network:

txt.decoy <- paste0("<img alt='mysrc=http://x.invalid/' src='", kImageLink, "'>")
expect_warning(res <- checkImageTag(txt.decoy),
               "invalid link which has been removed: http://x\.invalid/$")

Note the escaped .expect_warning matches its second argument as a regex.

(Confirmed RS-23603 exists and its summary matches this comment, so the ticket reference is sound.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took your suggestion verbatim in 29bfde7, escaped dot included: txt.decoy <- paste0("<img alt='mysrc=http://x.invalid/' src='", kImageLink, "'>") with the warning anchored on "invalid link which has been removed: http://x\.invalid/$". Added a note that the block deliberately carries no skipIfOffline() — the decoy is matched first, so kImageLink is never fetched and the block is genuinely network-free. Confirmed in the simulated-offline run: it passes there.

@chschan chschan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments, but otherwise LGTM

From chschan's review of PR #72:

- The offline guard missed the first test_that block, which fetches both the
  Dropbox URL and the wiki image, so the file still failed offline rather than
  skipping. The probe now caches per link, skipIfOffline() takes the links a
  block needs, and that block guards on both. Header NOTE corrected to say what
  is actually guarded.
- Replace the duplicated wiki URL literals with kImageLink in every block. The
  guard was previously correct only because the literals happened to match the
  probe's URL; changing kImageLink would have left those blocks fetching a
  stale URL while the probe reported reachable.
- "src location is independent of attribute order" overstated the behaviour -
  the decoy block shows it is not. Renamed to "src is found when it is the
  first attribute".
- Use expect_warning(..., NA) uniformly for the success-path calls. The
  expect_error(..., NA) form said nothing about warnings, so a warn-and-strip
  regression surfaced as a testthat warning plus a downstream equality failure
  rather than a clear one.
- The two syntax-error branches emit a byte-identical message, so the pinned
  echo could not distinguish them. Cover them with one input each instead: the
  existing input reaches the src= branch, and an input with no closing ">"
  misses the tag regex first.
- The decoy link was the bare string "x", so the branch depended on DNS
  failing to resolve a single-label host. Use http://x.invalid/ (RFC 2606), so
  it is inert on any network.
- Pin the empty-link branch's return value, which returns sub(imgtag, "",
  text) rather than the bare "" of the syntax-error branches - visible only
  with surrounding text.

Verified offline behaviour by pointing both link constants at an unreachable
host: 6 skips, 6 passes, 0 failures. With network: 28 passing, 0 failures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@SurreyHughesDisplayr

Copy link
Copy Markdown
Contributor Author

All seven inline comments plus the un-inlinable note are addressed in 29bfde7; PR description updated for the three stale claims (the "fails offline" wording, the syntax-error distinguishability claim, and the missing RS-23603 reference).

The one non-obvious call: I guarded the first test_that block rather than narrowing the header NOTE. That means its two GET-free assertions (txt.empty, txt.empty2) now skip offline along with the rest of the block, since testthat skips whole blocks. Splitting them into a separate block would keep them running, but restructures pre-existing tests for little gain — the header NOTE says so explicitly.

Offline verification (both link constants pointed at an unreachable host): 6 skips, 6 passes, 0 failures — previously the first block gave 3 hard failures. With network: 28 passing, 0 failures, 0 warnings.

@SurreyHughesDisplayr
SurreyHughesDisplayr merged commit 74ba80a into master Aug 26, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants