"""Journal Formatter and Journal Finder tabs (no network: the journal lookup and the search itself are not run).""" import json import os import sys import zipfile from pathlib import Path import pytest import finder_tab as FND import formatter_tab as FMT ROOT = Path(__file__).resolve().parent.parent progress = lambda *a, **k: None def _manuscript(tmp_path): from docx import Document d = Document() d.add_heading("A Hybrid Deep Learning Framework for Intrusion Detection", 0) d.add_paragraph("Ravi Kumar, Priya Sharma") d.add_paragraph("Abstract: This study proposes a hybrid model that reaches 98.4% accuracy on a public dataset.") d.add_heading("Introduction", 1) d.add_paragraph("Medical devices are increasingly connected (Smith, 2020). Prior work used shallow models.") d.add_heading("References", 1) d.add_paragraph("Smith, J. (2020). Deep learning for intrusion detection. Journal of Network Security, 12(3), " "45-67.") p = tmp_path / "my paper.docx" d.save(str(p)) return str(p) # ---------------------------------------------------------------- formatter def test_formatter_engine_is_available(): assert FMT.AVAILABLE, FMT.WHY assert len(FMT._preset_choices()) > 10 and FMT._preset_key(FMT.AUTO) is None def test_format_gives_word_latex_report_and_an_overleaf_button(tmp_path): style = FMT.PRESETS["ieee_journal"]["name"] status, dl, docx, zip_, md, overleaf, report = FMT.format_doc(_manuscript(tmp_path), "", style, None, "", "", True, True, False, FMT.REF_STYLES[0], FMT.CITE_MODES[0], progress) assert "Formatted as" in status and "checks passed" in status assert docx.endswith("my_paper_formatted.docx") and Path(docx).stat().st_size > 5000 names = zipfile.ZipFile(zip_).namelist() assert "main.tex" in names and not any(n.startswith("/") for n in names) # main.tex at the top level main = zipfile.ZipFile(zip_).read("main.tex").decode("utf-8") assert "\\documentclass" in main and "IEEEtran" in main and "\\begin{document}" in main assert Path(md).read_text(encoding="utf-8").startswith("# Formatting report") and "Compliance" in report # Overleaf: instructions only; the customer downloads the zip and uploads it himself assert "Upload Project" in overleaf and "= 4 for src, cache in ((data / "ext_list_Jul_2026.xlsx", data / "ext_list_Jul_2026.parsed.pkl"), (data / "scimagojr_2025.csv", data / "scimagojr_2025.parsed.pkl"), (data / "J_Medline.txt", data / ".pubmed_J_Medline.pkl")): assert cache.exists() and cache.stat().st_mtime >= src.stat().st_mtime, cache.name def test_both_tabs_take_the_suites_status_panel_and_log_their_runs(): import inspect for mod, tool in ((FMT, "Journal Formatter"), (FND, "Journal Finder")): src = inspect.getsource(mod.build) assert 'ui["run_live"]' in src and 'ui["wait"]' in src and 'ui["job"](status)' in src assert 'ui["log"]' in src and tool in src and 'ui["user"]' in src def test_job_options_map_to_the_pipeline_flags(): sys.path.insert(0, str(ROOT / "finder")) import run_job a = run_job.Args(title="A study of things", terms=["x"], limit=20, top=5, acceptance=False, turnaround=True, semantic=False, apc=True, quartiles=[1, 2], require_scopus=True, abdc_ranks=["A*", "A"], fast_days=60, sort_by="speed", special_issues=False) assert a.title == ["A", "study", "of", "things"] and a.term == ["x"] and a.limit == 20 and a.top == 5 assert a.no_scrape is True and a.no_turnaround is False and a.no_semantic is True and a.no_apc is False assert a.fast is True and a.quartile == [1, 2] and a.abdc_ranks == ["A*", "A"] and a.fast_days == 60 assert a.sort_by == "speed" and callable(a.progress) def test_shortlist_table_is_readable(tmp_path): csv = tmp_path / "shortlist.csv" csv.write_text("display_name,publisher,quartile,quartile_is_proxy,abdc_rank,in_pubmed,acceptance_rate_used," "acceptance_days,acceptance_speed,apc_status,apc_usd_est,term_matches,opportunity_score,homepage," "openalex_id\n" "Computers & Security,Elsevier,1,False,,False,0.21,112,Moderate,paid,3200,2,71.234," "https://example.org/cs,S1\n" "Journal of Things,Springer,2,True,A,True,,,,no APC,,1,55.5,https://example.org/jt,S2\n", encoding="utf-8") view, n = FND._table(csv) assert n == 2 and list(view.columns)[:3] == ["Journal", "Publisher", "Quartile"] and "openalex_id" not in view r1, r2 = view.iloc[0], view.iloc[1] assert r1["Quartile"] == "Q1" and r1["Acceptance rate"] == "21%" and r1["APC"] == "$3,200" and r1["PubMed"] == "—" assert r2["Quartile"] == "Q2 (estimate)" and r2["APC"] == "no APC" and r2["PubMed"] == "yes" and r2["ABDC"] == "A" def test_pubmed_column_with_gaps_is_not_all_yes(tmp_path): csv = tmp_path / "shortlist.csv" csv.write_text(chr(10).join(["display_name,in_pubmed", "A,False", "B,True", "C,", ""]), encoding="utf-8") view, _ = FND._table(csv) assert list(view["PubMed"]) == ["—", "yes", "—"] def test_download_buttons_hold_the_file(tmp_path): import base64 as b64 from tools_common import download_link, download_row f = tmp_path / "shortlist.csv" f.write_text("a,b", encoding="utf-8") h = download_link(str(f), "Download shortlist (.csv)") assert 'download="shortlist.csv"' in h and "Download shortlist (.csv)" in h assert b64.b64decode(h.split("base64,", 1)[1].split('"', 1)[0]) == b"a,b" assert download_link(None) == "" and download_link(str(tmp_path / "missing.csv")) == "" assert download_row((str(f), "One"), (None, "Two")).count("cs-dl") == 1 and download_row((None, "x")) == "" def test_find_asks_for_a_title_and_a_subject(): import gradio as gr blank = ["", [], "", ["Scopus"], [], [], 40, 10, 3, False, 90, "Best fit", True, True, True, True, 0, False] with pytest.raises(gr.Error): FND.find_journals(*blank, progress=progress) blank[0] = "A hybrid deep learning framework for intrusion detection" with pytest.raises(gr.Error): FND.find_journals(*blank, progress=progress) # title but no subject kept with pytest.raises(gr.Error): FND.detect_terms("too short") def test_both_tabs_are_part_of_the_app(): app = pytest.importorskip("app") labels = json.dumps(app.demo.get_config_file(), default=str) assert "Format my manuscript" in labels and "Find the best journals" in labels and "Where should the conference be?" in labels assert "cs-go-formatter" in labels and "cs-go-finder" in labels def test_a_reference_that_is_misread_keeps_the_authors_wording(): from formatmatch import refs as R text = lambda raw, style: "".join(x.text for x in R.format_reference(R.parse_reference(raw), style, 1)) book = "Mehta, P. K., Monteiro, P. J. M. Concrete: Microstructure, Properties, and Materials. McGraw-Hill, 2014." for raw in (book, "Neville, A. M. Properties of Concrete. Pearson, 2011.", "de Larrard, F. Concrete Mixture Proportioning: A Scientific Approach. E & FN Spon, London, 1999.", "Bureau of Indian Standards. IS 516: Method of tests for strength of concrete. New Delhi, 1959."): assert not R.parse_reference(raw).confident assert raw in text(raw, "apa") and raw in text("[7] " + raw, "ieee") # no surname or title word lost assert R.lost_words(R.parse_reference(book)) == {"mehta", "monteiro"} good = "Kumar, A., Rao, B., & Singh, C. (2019). Durability of blended cements. Cement and Concrete Research, 120, 45-56." assert R.parse_reference(good).confident # a clean entry is still restyled assert text(good, "ieee").startswith("[1]\tA. Kumar, B. Rao, and C. Singh, “Durability of blended cements,”") # ---------------------------------------------------------------- LaTeX for Overleaf def test_latex_checker_finds_what_stops_a_compile(): from formatmatch.texlint import lint good = ("\\begin{document}\\section{A} Text \\cite{r1} $x$ \\begin{figure}\\includegraphics{figures/f.png}" "\\caption{c}\\end{figure}\\begin{thebibliography}{1}\\bibitem{r1} X.\\end{thebibliography}\\end{document}") assert lint(good, {"figures/f.png": b""}) == [] bad = lint("\\begin{document}\\begin{table}\\begin{tabular}{l}a\\end{table} {x \\cite{r9} " "\\includegraphics{figures/missing.png}\\begin{thebibliography}{1}\\bibitem{r1} X." "\\end{thebibliography}\\end{document}", {}) joined = " | ".join(bad) for part in ("tabular", "opening brace", "missing.png is not in the project", "r9", "no caption"): assert part in joined, (part, bad) def test_two_column_pdf_lines_are_not_joined_across_the_gutter(): from formatmatch import read_pdf as RP line = lambda x0, x1, y: {"bbox": (x0, y, x1, y + 10), "spans": [{"text": "word " * 8, "size": 10, "flags": 0}]} blk = lambda x0, x1, ys: {"bbox": (x0, ys[0], x1, ys[-1] + 10), "lines": [line(x0, x1, y) for y in ys]} ys = list(range(100, 300, 12)) left, right = blk(50, 295, ys), blk(310, 555, ys) # an IEEE page: 15 pt between the columns g = RP._gutter([left, right], 612) assert g and 295 <= g <= 310 merged = RP._merge_baselines([left, right], 10, g) assert len(merged) == 2 and len(merged[0]["lines"]) == len(ys) # still two columns assert RP._order_blocks(merged, 612, g)[0] is merged[0] assert RP._gutter([blk(50, 555, ys)], 612) is None # a one-column page has no gutter # without the gutter the same lines were glued together (the old behaviour this guards against) assert len(RP._merge_baselines([blk(50, 295, ys), blk(310, 555, ys)], 10, None)) == 1 def test_pdf_pieces_cut_by_a_column_are_put_back_together(): from formatmatch import model as M, read_pdf as RP B = lambda role, t: M.Block(role, runs=[M.Run(t)], orig=t) assert RP._continues(B(M.REFERENCE, "[2] W. Tushar, T.K. Saha, Peer-to-peer trading"), B(M.REFERENCE, "in electricity networks, 2020.")) assert not RP._continues(B(M.REFERENCE, "[2] W. Tushar, Title, 2020."), B(M.REFERENCE, "[3] T. Morstyn, Title.")) assert RP._continues(B(M.PARA, "This paper fills these gaps with a personalised federated"), B(M.PARA, "MARL mechanism".lower())) assert not RP._continues(B(M.PARA, "A finished sentence."), B(M.PARA, "another one")) assert RP._continues(B(M.CAPTION, "LEARNING HYPERPARAMETERS (IDENTICAL FOR ALL"), B(M.PARA, "LEARNING METHODS)")) assert RP._soft_hyphen("heteroge-", "neous") and not RP._soft_hyphen("peer-to-", "peer") def test_sections_one_level_too_low_become_sections_and_floats_stay_in_the_column(): from formatmatch import model as M, pipeline as PL H = lambda t, lvl: M.Block(M.HEADING, runs=[M.Run(t)], level=lvl, orig=t) blocks = [M.Block(M.TITLE, runs=[M.Run("A Paper")]), H("Introduction", 2), H("Background", 3), H("Results", 2), H("References", 1)] rep = PL.Report() PL._promote_headings(blocks, rep) assert [b.level for b in blocks[1:]] == [1, 2, 1, 1] and any("moved up" in c for c in rep.changes) ok = [H("Introduction", 1), H("Method", 2)] PL._promote_headings(ok, PL.Report()) assert [b.level for b in ok] == [1, 2] # a sound outline is left alone # figure, caption, figure, caption: each caption takes the figure before it F = lambda n: M.Block(M.FIGURE, image=b"x" + bytes([n]), image_ext="png") C = lambda n: M.Block(M.CAPTION, runs=[M.Run(f"cap {n}")], cap_kind="figure", cap_num=n, orig=f"Fig. {n}. cap {n}") f1, c1, f2, c2 = F(1), C(1), F(2), C(2) out = PL._pair_floats([f1, c1, f2, c2], {"captions": {}}, PL.Report()) assert [id(x) for x in out] == [id(f1), id(c1), id(f2), id(c2)] def test_latex_keeps_equation_pictures_and_tables_inside_the_line(): from formatmatch import refs as R src = open(os.path.join(os.path.dirname(FMT.__file__), "formatmatch", "render_latex.py"), encoding="utf-8").read() assert "newcommand{" + chr(92) * 2 + "fmimg}" in src and "newcommand{" + chr(92) * 2 + "fmfit}" in src assert "includegraphics[height={h" not in src # the fixed-height picture that overflowed the column r = R.parse_reference("[2] W. Tushar, T.K. Saha, H.V. Poor, Peer-to-peer trading in electricity networks: An " "overview, IEEE Transactions on Smart Grid 11 (4) (2020) 3185–3200.") assert r.confident and r.authors[0] == ("Tushar", "W.") and r.container == "IEEE Transactions on Smart Grid" assert (r.volume, r.issue, r.year, r.pages) == ("11", "4", "2020", "3185–3200") def test_long_equations_are_fitted_to_the_column_in_latex_and_word(tmp_path): import io from PIL import Image from formatmatch.render_latex import LatexRenderer as LR assert LR.fit_math("a+b", False) == "\\fmmath{a+b}" assert LR.fit_math("a+b", True).startswith("\\fmmath[\\dimexpr\\linewidth-3.2em\\relax]{a+b") pic = "\\vcenter{\\hbox{\\fmimg{9.0pt}{equations/eq1.png}}}" assert LR.fit_math(pic, True) == pic and LR.fit_math("", False) == "" # pictures fit themselves # Word: a picture 10 in wide is shrunk to the column; a small one keeps its size from formatmatch import render_docx as RD import docx buf = io.BytesIO(); Image.new("RGB", (2000, 100), "white").save(buf, "PNG") class Fake: _col_width_cm = lambda self: 8.0 _picture = RD.DocxRenderer._picture if hasattr(RD, "DocxRenderer") else None cls = next(v for v in vars(RD).values() if isinstance(v, type) and hasattr(v, "_picture")) Fake._picture = cls._picture d = docx.Document(); p = d.add_paragraph() Fake()._picture(p, {"blob": buf.getvalue(), "h": 0.5}) # 0.5 in tall -> 10 in wide Fake()._picture(p, {"blob": buf.getvalue(), "h": 0.05}) # 1 in wide: fits wide, small = d.inline_shapes[0], d.inline_shapes[1] assert abs(wide.width.inches - 8.0 / 2.54 * 0.82) < 0.02 and wide.height.inches < 0.2 assert abs(small.height.inches - 0.05) < 0.005