Skip to content

fix(theme): close four parity gaps between the bootstrap theme and the JSP search pages - #3463

Merged
marevol merged 4 commits into
mainfrom
fix/static-theme-parity-gaps
Sep 21, 2026
Merged

marevol merged 4 commits into
mainfrom
fix/static-theme-parity-gaps

Conversation

@marevol

@marevol marevol commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes four places where the bundled bootstrap static theme, now the default search UI (#3460), still differs from the JSP search pages.

Changes

  • Click-log order. /go/ links carry the 0-based position of the result on the page, the same value as the link's data-order, as the JSP pages sent. The theme sent a 1-based rank, so ClickLog.order was shifted by one.
  • Page offset in the URL. Paging writes start= to the address bar, so reload, back/forward and shared links land on the same page. Only start is written; the page does not go through the router, which would drop the facet selections kept in memory. A filter change that returns to the first page removes a stale start= in place.
  • OpenSearch description link. /api/v2/ui/config returns a new features.osdd_link flag, set from OsddHelper#hasOpenSearchFile() as the JSP osddLink was. The theme adds <link rel="search"> only when it is true, so the link is no longer advertised when osdd.link.enabled turns the document off (including auto under SSO).
  • Translations. Five error.detail_* messages were still in English in the de, es, fr, ko, pt-BR and zh-CN bundles.

Verification

  • mvn test -Dtest='UiConfigHandlerTest,LabelMessageThemeParityTest,BundledBootstrapThemeTest,OsddHelperTest' and mvn test -Dtest='StaticTheme*,ThemeManifestTest,ThemeRegistryTest,org.codelibs.fess.api.v2.**.*Test': 873 tests passed.
  • Theme JS tests (src/test/js, npx vitest run): 18 files, 572 tests passed.
  • mvn formatter:format and mvn license:format leave no changes.

The bootstrap static theme built /go/ links with a 1-based order while
the link's data-order stayed 0-based. The JSP search page sent the
0-based loop index (the same value as data-order), and GoAction stores
it as ClickLog.order, so click logs from the static theme were off by
one. Pass the 0-based position so both UIs record the same order.
The de, es, fr, ko, pt-BR and zh-CN bundles of the bootstrap static
theme still carried the English text for the error.detail_* keys shown
on the error page (docid_not_found, document_not_found,
not_found_on_file_system, unauthorized_request, bad_authentication).
Translate them, reusing the terms of the matching errors.* entries in
the core fess_message_* bundles.
Paging in the bootstrap static theme only re-ran the search, so start=
never reached the address bar and a reload, back/forward or shared link
fell back to the first page. The JSP paging links carried start=, and
runFromUrl() already reads it.

Push the new start= onto history when paging, and correct it in place
whenever a search runs with a different offset (a facet or filter change
resets to the first page). The URL is written directly rather than via
navigate(): the runFromUrl() it dispatches clears the in-memory facet
selections, and it would also fetch the page a second time.
The bootstrap static theme always added <link rel="search" href="osdd">,
while the JSP pages emitted it only when OsddHelper#hasOpenSearchFile()
was true (osdd.link.enabled, which in auto mode turns it off under SSO).
With the document not served, browsers were pointed at a missing OSDD.

Expose the same decision as features.osdd_link in /api/v2/ui/config,
document it in the user OpenAPI spec, and gate the link on it.
@marevol marevol added this to the 15.9.0 milestone Sep 21, 2026
@marevol marevol self-assigned this Sep 21, 2026
@marevol
marevol merged commit 8a9e1e1 into main Sep 21, 2026
2 checks passed
marevol added a commit to codelibs/fess-test-ui that referenced this pull request Sep 21, 2026
codelibs/fess#3460 makes the bundled bootstrap static-theme SPA the page
for /, /search, /help, /profile, /cache, /advance and /error/*, and
renders errors in place at the requested URL with the real HTTP status
instead of redirecting through error/redirect.jsp to a 200 page. The
search modules and four /admin/general/ leaves asserted the JSP pages
and the redirect, so they fail against the snapshot image. Rewrite them
against the new contract:

- Wait for the SPA to render (wait_for_selector / expect_response)
  instead of reading the DOM right after the load, and address the home
  form by id (#contentQuery, #home-search-submit): the hidden header copy
  of input[name=q] comes first in the DOM.
- Compare SPA text with the theme's own bundle
  (themes/bootstrap/i18n/messages.<locale>.json) through a small helper,
  search/_theme.py; server messages in API error responses still go
  through tm().
- root_top, form_submit, advance, profile_form, no_results, suggest,
  sort, facet and cache follow the new markup: relative hrefs resolved
  against <base href>, pushState navigation, compose() building the
  advanced query client-side, the client-side password mismatch check,
  the facet click re-running /api/v2/search with ex_q, and the cached
  copy shown in a sandboxed iframe.
- query_errors expects HTTP 400 from /api/v2/search and the message in
  #search-error at the unchanged /search/ URL.
- error_pages, go_click and jsonResponse assert the status, the
  X-Fess-Error-Code header and the unchanged URL, now that they are
  observable; /error/badrequrest/ is simply the catch-all 500.
- osdd waits for the <link rel="search"> the SPA adds and expects the
  site name from /api/v2/ui/config as its title.
- root_top checks that no [data-i18n] element still shows its own key
  (the SPA's untranslated-string symptom) instead of LastaFlute's
  ???labels.x??? marker, which the SPA never renders.
- loginLink, notificationSearchTop and loginRequired observe a guest
  page (FessContext.guest_page: a separate browser context without the
  suite's cookies), since the SPA renders the login link and the banner
  from /api/v2/ui/config and gates a guest behind an unclosable login
  modal instead of redirecting to /login/. The config fields and the
  rendered DOM are both asserted.
- query waits for the results as well.

Two assertions pin codelibs/fess#3463: pagination expects the URL to
record the page (start=<page size>), and go_click expects the /go/ order
parameter to be the result's 0-based data-order, as the JSP pages sent
it. Each runs last in its module so the checks before it still report
on an image built before that fix.
marevol added a commit to codelibs/fess-test-ui that referenced this pull request Sep 22, 2026
codelibs/fess#3460 makes the bundled bootstrap static-theme SPA the page
for /, /search, /help, /profile, /cache, /advance and /error/*, and
renders errors in place at the requested URL with the real HTTP status
instead of redirecting through error/redirect.jsp to a 200 page. The
search modules and four /admin/general/ leaves asserted the JSP pages
and the redirect, so they fail against the snapshot image. Rewrite them
against the new contract:

- Wait for the SPA to render (wait_for_selector / expect_response)
  instead of reading the DOM right after the load, and address the home
  form by id (#contentQuery, #home-search-submit): the hidden header copy
  of input[name=q] comes first in the DOM.
- Compare SPA text with the theme's own bundle
  (themes/bootstrap/i18n/messages.<locale>.json) through a small helper,
  search/_theme.py; server messages in API error responses still go
  through tm().
- root_top, form_submit, advance, profile_form, no_results, suggest,
  sort, facet and cache follow the new markup: relative hrefs resolved
  against <base href>, pushState navigation, compose() building the
  advanced query client-side, the client-side password mismatch check,
  the facet click re-running /api/v2/search with ex_q, and the cached
  copy shown in a sandboxed iframe.
- facet compares counts only within one search response: the badge with
  the label count of the search that rendered it, and the click's hit
  count with the label count of the click's own search. The seed's crawl
  keeps indexing after search_seed returns, so counts from separate
  searches can differ.
- query_errors expects HTTP 400 from /api/v2/search and the message in
  #search-error at the unchanged /search/ URL.
- error_pages, go_click and jsonResponse assert the status, the
  X-Fess-Error-Code header and the unchanged URL, now that they are
  observable; /error/badrequrest/ is simply the catch-all 500.
- osdd waits for the <link rel="search"> the SPA adds and expects the
  site name from /api/v2/ui/config as its title.
- root_top checks that no [data-i18n] element still shows its own key
  (the SPA's untranslated-string symptom) instead of LastaFlute's
  ???labels.x??? marker, which the SPA never renders.
- loginLink, notificationSearchTop and loginRequired observe a guest
  page (FessContext.guest_page: a separate browser context without the
  suite's cookies), since the SPA renders the login link and the banner
  from /api/v2/ui/config and gates a guest behind an unclosable login
  modal instead of redirecting to /login/. The config fields and the
  rendered DOM are both asserted.
- query waits for the results as well.

Two assertions pin codelibs/fess#3463: pagination expects the URL to
record the page (start=<page size>), and go_click expects the /go/ order
parameter to be the result's 0-based data-order, as the JSP pages sent
it. Each runs last in its module so the checks before it still report
on an image built before that fix.
marevol added a commit to codelibs/fess-test-ui that referenced this pull request Sep 22, 2026
#61)

* test(search): follow the static-theme default and in-place error pages

codelibs/fess#3460 makes the bundled bootstrap static-theme SPA the page
for /, /search, /help, /profile, /cache, /advance and /error/*, and
renders errors in place at the requested URL with the real HTTP status
instead of redirecting through error/redirect.jsp to a 200 page. The
search modules and four /admin/general/ leaves asserted the JSP pages
and the redirect, so they fail against the snapshot image. Rewrite them
against the new contract:

- Wait for the SPA to render (wait_for_selector / expect_response)
  instead of reading the DOM right after the load, and address the home
  form by id (#contentQuery, #home-search-submit): the hidden header copy
  of input[name=q] comes first in the DOM.
- Compare SPA text with the theme's own bundle
  (themes/bootstrap/i18n/messages.<locale>.json) through a small helper,
  search/_theme.py; server messages in API error responses still go
  through tm().
- root_top, form_submit, advance, profile_form, no_results, suggest,
  sort, facet and cache follow the new markup: relative hrefs resolved
  against <base href>, pushState navigation, compose() building the
  advanced query client-side, the client-side password mismatch check,
  the facet click re-running /api/v2/search with ex_q, and the cached
  copy shown in a sandboxed iframe.
- facet compares counts only within one search response: the badge with
  the label count of the search that rendered it, and the click's hit
  count with the label count of the click's own search. The seed's crawl
  keeps indexing after search_seed returns, so counts from separate
  searches can differ.
- query_errors expects HTTP 400 from /api/v2/search and the message in
  #search-error at the unchanged /search/ URL.
- error_pages, go_click and jsonResponse assert the status, the
  X-Fess-Error-Code header and the unchanged URL, now that they are
  observable; /error/badrequrest/ is simply the catch-all 500.
- osdd waits for the <link rel="search"> the SPA adds and expects the
  site name from /api/v2/ui/config as its title.
- root_top checks that no [data-i18n] element still shows its own key
  (the SPA's untranslated-string symptom) instead of LastaFlute's
  ???labels.x??? marker, which the SPA never renders.
- loginLink, notificationSearchTop and loginRequired observe a guest
  page (FessContext.guest_page: a separate browser context without the
  suite's cookies), since the SPA renders the login link and the banner
  from /api/v2/ui/config and gates a guest behind an unclosable login
  modal instead of redirecting to /login/. The config fields and the
  rendered DOM are both asserted.
- query waits for the results as well.

Two assertions pin codelibs/fess#3463: pagination expects the URL to
record the page (start=<page size>), and go_click expects the /go/ order
parameter to be the result's 0-based data-order, as the JSP pages sent
it. Each runs last in its module so the checks before it still report
on an image built before that fix.

* test: run the JSP-era tests on Fess 15.8 and earlier

The fess15 variants run Fess 15.8.0, which still serves the JSP search
pages, so the modules rewritten for the static-theme SPA cannot pass
there. Rather than skipping them, which would drop their coverage on the
15.x line, keep the previous tests and pick one by the Fess version.

- The fifteen search modules and the jsonResponse, loginLink,
  notificationSearchTop and loginRequired leaves keep their JSP-era
  version next to them as <module>_jsp.py, byte-identical to main, and
  their run() starts with `if run_jsp_variant(context, <module>_jsp):
  return`. Both lines therefore run real assertions.
- fess.test.ui.version asks the server once per run for its version:
  every /api/admin/* response is an ApiResult whose envelope carries
  the product version (major.minor), including the 401 an
  unauthenticated caller gets, on 15.8.0 as on the snapshot. The compose
  file cannot tell, since the snapshot tag names no version. When the
  version cannot be read, it raises instead of picking a line.
- STATIC_THEME_SINCE = (15, 9) is the one gate. The *_jsp.py modules,
  the switch and the label/message keys only they use (now marked as
  such) can go once no workflow targets Fess 15.8 or earlier.
- tests/ui/test_version.py covers the parsing, the detection and its
  failure modes, the dispatch on either side of the gate, and that every
  switched module names an importable <module>_jsp.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant