11import { describe , expect , it } from "vitest" ;
22import type { BookSearchResultDto as BookSearchResult } from "../../types/dto" ;
3- import { calculateTechScore , rankTechBooks } from "./tech-book-search" ;
3+ import {
4+ calculateTechScore ,
5+ isLikelyTechBook ,
6+ rankTechBooks ,
7+ type ScoreReason ,
8+ } from "./tech-book-search" ;
49
510const TEST_NOW = new Date ( 2026 , 5 , 29 ) ;
611
@@ -388,8 +393,41 @@ describe("calculateTechScore", () => {
388393 ) ;
389394} ) ;
390395
396+ describe ( "isLikelyTechBook" , ( ) => {
397+ it ( "should keep a book with technical evidence and a positive score" , ( ) => {
398+ expect ( isLikelyTechBook ( 15 , [ { type : "title_keyword" , label : "Docker" , score : 15 } ] ) ) . toBe (
399+ true ,
400+ ) ;
401+ } ) ;
402+
403+ it ( "should reject a book that only has publication recency" , ( ) => {
404+ expect (
405+ isLikelyTechBook ( 12 , [ { type : "recent_publication" , label : "1年以内" , score : 12 } ] ) ,
406+ ) . toBe ( false ) ;
407+ } ) ;
408+
409+ it ( "should reject a book with no score reasons" , ( ) => {
410+ expect ( isLikelyTechBook ( 0 , [ ] ) ) . toBe ( false ) ;
411+ } ) ;
412+
413+ it ( "should reject a book whose net technical score is not positive" , ( ) => {
414+ const reasons : ScoreReason [ ] = [
415+ { type : "title_keyword" , label : "Docker" , score : 15 } ,
416+ { type : "negative_title_keyword" , label : "レシピ" , score : - 40 } ,
417+ ] ;
418+
419+ expect ( isLikelyTechBook ( - 25 , reasons ) ) . toBe ( false ) ;
420+ } ) ;
421+
422+ it ( "should reject a book with only negative keywords" , ( ) => {
423+ expect (
424+ isLikelyTechBook ( - 40 , [ { type : "negative_title_keyword" , label : "小説" , score : - 40 } ] ) ,
425+ ) . toBe ( false ) ;
426+ } ) ;
427+ } ) ;
428+
391429describe ( "rankTechBooks" , ( ) => {
392- it ( "should rank a technical book before a newer non-technical book" , ( ) => {
430+ it ( "should exclude a newer non-technical book from ranked results " , ( ) => {
393431 const books = [
394432 createBook ( {
395433 title : "新しい小説" ,
@@ -405,11 +443,11 @@ describe("rankTechBooks", () => {
405443
406444 const ranked = rankTechBooks ( books , "react" , { now : TEST_NOW } ) ;
407445
408- expect ( ranked [ 0 ] . title ) . toBe ( "React設計パターン" ) ;
409- expect ( ranked [ 0 ] . techScore ) . toBeGreaterThan ( ranked [ 1 ] . techScore ) ;
446+ expect ( ranked . map ( ( book ) => book . title ) ) . toEqual ( [ "React設計パターン" ] ) ;
447+ expect ( ranked [ 0 ] . techScore ) . toBeGreaterThan ( 0 ) ;
410448 } ) ;
411449
412- it ( "should sort books by technical score in descending order" , ( ) => {
450+ it ( "should sort remaining technical books by technical score in descending order" , ( ) => {
413451 const books = [
414452 createBook ( { title : "Neutral Book" } ) ,
415453 createBook ( { title : "Docker入門" } ) ,
@@ -418,34 +456,46 @@ describe("rankTechBooks", () => {
418456
419457 const ranked = rankTechBooks ( books , "query" , { now : TEST_NOW } ) ;
420458
421- expect ( ranked . map ( ( book ) => book . title ) ) . toEqual ( [
422- "TypeScript入門" ,
423- "Docker入門" ,
424- "Neutral Book" ,
425- ] ) ;
459+ expect ( ranked . map ( ( book ) => book . title ) ) . toEqual ( [ "TypeScript入門" , "Docker入門" ] ) ;
460+ } ) ;
461+
462+ it ( "should exclude books without technical evidence even if they are recent" , ( ) => {
463+ const books = [
464+ createBook ( {
465+ title : "話題のエッセイ" ,
466+ publisher : "一般出版社" ,
467+ publishedDate : "2026年6月1日" ,
468+ } ) ,
469+ createBook ( { title : "漫画入門" , publishedDate : "2026年5月1日" } ) ,
470+ createBook ( { title : "Python実践入門" , publishedDate : "2018年1月1日" } ) ,
471+ ] ;
472+
473+ const ranked = rankTechBooks ( books , "python" , { now : TEST_NOW } ) ;
474+
475+ expect ( ranked . map ( ( book ) => book . title ) ) . toEqual ( [ "Python実践入門" ] ) ;
426476 } ) ;
427477
428478 it ( "should prefer the newer publication when technical scores are tied" , ( ) => {
429479 const books = [
430- createBook ( { title : "Older" , publishedDate : "2019年1月1日" } ) ,
431- createBook ( { title : "Newer" , publishedDate : "2020年1月1日" } ) ,
480+ createBook ( { title : "Older Docker " , publishedDate : "2019年1月1日" } ) ,
481+ createBook ( { title : "Newer Docker " , publishedDate : "2020年1月1日" } ) ,
432482 ] ;
433483
434484 const ranked = rankTechBooks ( books , "query" , { now : TEST_NOW } ) ;
435485
436- expect ( ranked . map ( ( book ) => book . title ) ) . toEqual ( [ "Newer" , "Older" ] ) ;
486+ expect ( ranked . map ( ( book ) => book . title ) ) . toEqual ( [ "Newer Docker " , "Older Docker " ] ) ;
437487 expect ( ranked [ 0 ] . techScore ) . toBe ( ranked [ 1 ] . techScore ) ;
438488 } ) ;
439489
440490 it ( "should place an unknown publication date after a valid date when scores are tied" , ( ) => {
441491 const books = [
442- createBook ( { title : "Unknown" , publishedDate : "unknown" } ) ,
443- createBook ( { title : "Known" , publishedDate : "2020年1月1日" } ) ,
492+ createBook ( { title : "Unknown Docker " , publishedDate : "unknown" } ) ,
493+ createBook ( { title : "Known Docker " , publishedDate : "2020年1月1日" } ) ,
444494 ] ;
445495
446496 const ranked = rankTechBooks ( books , "query" , { now : TEST_NOW } ) ;
447497
448- expect ( ranked . map ( ( book ) => book . title ) ) . toEqual ( [ "Known" , "Unknown" ] ) ;
498+ expect ( ranked . map ( ( book ) => book . title ) ) . toEqual ( [ "Known Docker " , "Unknown Docker " ] ) ;
449499 } ) ;
450500
451501 it ( "should omit score reasons by default" , ( ) => {
0 commit comments