mirror of
https://github.com/discourse/discourse-rewind.git
synced 2025-12-11 02:05:32 +00:00
DEV: Adding specs and refactors (#21)
Add specs for the following reports: * Reading time * Activity calendar * Best posts * Best topics * Top words * Most viewed tags * Most viewed categories Did some minor UI and ruby refactors for related components. Also made a minor change to the Activity calendar, to show a title based on the number of posts or if the user was active on hover. Still missing specs for: * Reactions * FBFF And the newly added reports that don't yet have UI components.
This commit is contained in:
parent
09ee91ad67
commit
26a77d4c69
@ -20,9 +20,13 @@ module DiscourseRewind
|
||||
TopicViewItem
|
||||
.joins(:topic)
|
||||
.joins("INNER JOIN categories ON categories.id = topics.category_id")
|
||||
.where(user: user)
|
||||
.where(viewed_at: date)
|
||||
.where(categories: { id: user.guardian.allowed_category_ids })
|
||||
.where(
|
||||
user: user,
|
||||
viewed_at: date,
|
||||
categories: {
|
||||
id: user.guardian.allowed_category_ids,
|
||||
},
|
||||
)
|
||||
.group("categories.id, categories.name")
|
||||
.order("COUNT(*) DESC")
|
||||
.limit(4)
|
||||
|
||||
@ -22,9 +22,7 @@ module DiscourseRewind
|
||||
.joins(:topic)
|
||||
.joins("INNER JOIN topic_tags ON topic_tags.topic_id = topics.id")
|
||||
.joins("INNER JOIN tags ON tags.id = topic_tags.tag_id")
|
||||
.where(user: user)
|
||||
.where(viewed_at: date)
|
||||
.where(tags: { id: Tag.visible(user.guardian).pluck(:id) })
|
||||
.where(user: user, viewed_at: date, tags: { id: Tag.visible(user.guardian).pluck(:id) })
|
||||
.group("tags.id, tags.name")
|
||||
.order("COUNT(DISTINCT topic_views.topic_id) DESC")
|
||||
.limit(4)
|
||||
|
||||
@ -85,7 +85,7 @@ module DiscourseRewind
|
||||
end
|
||||
|
||||
def sort_and_limit(reactions)
|
||||
reactions.sort_by { |_, v| -v }.first(5).reverse.to_h
|
||||
reactions.sort_by { |_, value| -value }.take(5).reverse.to_h
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -5,6 +5,144 @@
|
||||
module DiscourseRewind
|
||||
module Action
|
||||
class ReadingTime < BaseReport
|
||||
POPULAR_BOOKS = {
|
||||
"The Metamorphosis" => {
|
||||
reading_time: 3120,
|
||||
isbn: "978-0553213690",
|
||||
series: false,
|
||||
},
|
||||
"The Little Prince" => {
|
||||
reading_time: 5400,
|
||||
isbn: "978-0156012195",
|
||||
series: false,
|
||||
},
|
||||
"Animal Farm" => {
|
||||
reading_time: 7200,
|
||||
isbn: "978-0451526342",
|
||||
series: false,
|
||||
},
|
||||
"The Alchemist" => {
|
||||
reading_time: 10_800,
|
||||
isbn: "978-0061122415",
|
||||
series: false,
|
||||
},
|
||||
"The Great Gatsby" => {
|
||||
reading_time: 12_600,
|
||||
isbn: "978-0743273565",
|
||||
series: false,
|
||||
},
|
||||
"The Hitchhiker's Guide to the Galaxy" => {
|
||||
reading_time: 12_600,
|
||||
isbn: "978-0345391803",
|
||||
series: false,
|
||||
},
|
||||
"Fahrenheit 451" => {
|
||||
reading_time: 15_000,
|
||||
isbn: "978-1451673319",
|
||||
series: false,
|
||||
},
|
||||
"And Then There Were None" => {
|
||||
reading_time: 16_200,
|
||||
isbn: "978-0062073488",
|
||||
series: false,
|
||||
},
|
||||
"1984" => {
|
||||
reading_time: 16_800,
|
||||
isbn: "978-0451524935",
|
||||
series: false,
|
||||
},
|
||||
"The Catcher in the Rye" => {
|
||||
reading_time: 18_000,
|
||||
isbn: "978-0316769488",
|
||||
series: false,
|
||||
},
|
||||
"The Hunger Games" => {
|
||||
reading_time: 19_740,
|
||||
isbn: "978-0439023481",
|
||||
series: false,
|
||||
},
|
||||
"To Kill a Mockingbird" => {
|
||||
reading_time: 22_800,
|
||||
isbn: "978-0061120084",
|
||||
series: false,
|
||||
},
|
||||
"Harry Potter and the Sorcerer's Stone" => {
|
||||
reading_time: 24_600,
|
||||
isbn: "978-0590353427",
|
||||
series: true,
|
||||
},
|
||||
"Pride and Prejudice" => {
|
||||
reading_time: 25_200,
|
||||
isbn: "978-1503290563",
|
||||
series: false,
|
||||
},
|
||||
"The Hobbit" => {
|
||||
reading_time: 27_000,
|
||||
isbn: "978-0547928227",
|
||||
series: false,
|
||||
},
|
||||
"Little Women" => {
|
||||
reading_time: 30_000,
|
||||
isbn: "978-0147514011",
|
||||
series: false,
|
||||
},
|
||||
"Jane Eyre" => {
|
||||
reading_time: 34_200,
|
||||
isbn: "978-0141441146",
|
||||
series: false,
|
||||
},
|
||||
"The Da Vinci Code" => {
|
||||
reading_time: 37_800,
|
||||
isbn: "978-0307474278",
|
||||
series: false,
|
||||
},
|
||||
"One Hundred Years of Solitude" => {
|
||||
reading_time: 46_800,
|
||||
isbn: "978-0060883287",
|
||||
series: false,
|
||||
},
|
||||
"The Lord of the Rings" => {
|
||||
reading_time: 108_000,
|
||||
isbn: "978-0544003415",
|
||||
series: true,
|
||||
},
|
||||
"The Complete works of Shakespeare" => {
|
||||
reading_time: 180_000,
|
||||
isbn: "978-1853268953",
|
||||
series: true,
|
||||
},
|
||||
"The Game of Thrones Series" => {
|
||||
reading_time: 360_000,
|
||||
isbn: "978-0007477159",
|
||||
series: true,
|
||||
},
|
||||
"Malazan Book of the Fallen" => {
|
||||
reading_time: 720_000,
|
||||
isbn: "978-0765348821",
|
||||
series: true,
|
||||
},
|
||||
"Terry Pratchett's Discworld series" => {
|
||||
reading_time: 1_440_000,
|
||||
isbn: "978-9123684458",
|
||||
series: true,
|
||||
},
|
||||
"The Wandering Inn web series" => {
|
||||
reading_time: 2_160_000,
|
||||
isbn: "the-wandering-inn",
|
||||
series: true,
|
||||
},
|
||||
"The Combined Cosmere works + Wheel of Time" => {
|
||||
reading_time: 2_880_000,
|
||||
isbn: "978-0812511819",
|
||||
series: true,
|
||||
},
|
||||
"The Star Trek novels" => {
|
||||
reading_time: 3_600_000,
|
||||
isbn: "978-1852860691",
|
||||
series: true,
|
||||
},
|
||||
}.symbolize_keys
|
||||
|
||||
FakeData = {
|
||||
data: {
|
||||
reading_time: 2_880_000,
|
||||
@ -26,7 +164,7 @@ module DiscourseRewind
|
||||
{
|
||||
data: {
|
||||
reading_time: reading_time,
|
||||
book: book[:title],
|
||||
book: book[:title].to_s,
|
||||
isbn: book[:isbn],
|
||||
series: book[:series],
|
||||
},
|
||||
@ -34,168 +172,15 @@ module DiscourseRewind
|
||||
}
|
||||
end
|
||||
|
||||
def popular_books
|
||||
{
|
||||
"The Hunger Games" => {
|
||||
reading_time: 19_740,
|
||||
isbn: "978-0439023481",
|
||||
series: false,
|
||||
},
|
||||
"The Metamorphosis" => {
|
||||
reading_time: 3120,
|
||||
isbn: "978-0553213690",
|
||||
series: false,
|
||||
},
|
||||
"To Kill a Mockingbird" => {
|
||||
reading_time: 22_800,
|
||||
isbn: "978-0061120084",
|
||||
series: false,
|
||||
},
|
||||
"Pride and Prejudice" => {
|
||||
reading_time: 25_200,
|
||||
isbn: "978-1503290563",
|
||||
series: false,
|
||||
},
|
||||
"1984" => {
|
||||
reading_time: 16_800,
|
||||
isbn: "978-0451524935",
|
||||
series: false,
|
||||
},
|
||||
"The Lord of the Rings" => {
|
||||
reading_time: 108_000,
|
||||
isbn: "978-0544003415",
|
||||
series: true,
|
||||
},
|
||||
"Harry Potter and the Sorcerer's Stone" => {
|
||||
reading_time: 24_600,
|
||||
isbn: "978-0590353427",
|
||||
series: true,
|
||||
},
|
||||
"The Great Gatsby" => {
|
||||
reading_time: 12_600,
|
||||
isbn: "978-0743273565",
|
||||
series: false,
|
||||
},
|
||||
"The Little Prince" => {
|
||||
reading_time: 5400,
|
||||
isbn: "978-0156012195",
|
||||
series: false,
|
||||
},
|
||||
"Animal Farm" => {
|
||||
reading_time: 7200,
|
||||
isbn: "978-0451526342",
|
||||
series: false,
|
||||
},
|
||||
"The Catcher in the Rye" => {
|
||||
reading_time: 18_000,
|
||||
isbn: "978-0316769488",
|
||||
series: false,
|
||||
},
|
||||
"Jane Eyre" => {
|
||||
reading_time: 34_200,
|
||||
isbn: "978-0141441146",
|
||||
series: false,
|
||||
},
|
||||
"Fahrenheit 451" => {
|
||||
reading_time: 15_000,
|
||||
isbn: "978-1451673319",
|
||||
series: false,
|
||||
},
|
||||
"The Hobbit" => {
|
||||
reading_time: 27_000,
|
||||
isbn: "978-0547928227",
|
||||
series: false,
|
||||
},
|
||||
"The Da Vinci Code" => {
|
||||
reading_time: 37_800,
|
||||
isbn: "978-0307474278",
|
||||
series: false,
|
||||
},
|
||||
"Little Women" => {
|
||||
reading_time: 30_000,
|
||||
isbn: "978-0147514011",
|
||||
series: false,
|
||||
},
|
||||
"One Hundred Years of Solitude" => {
|
||||
reading_time: 46_800,
|
||||
isbn: "978-0060883287",
|
||||
series: false,
|
||||
},
|
||||
"And Then There Were None" => {
|
||||
reading_time: 16_200,
|
||||
isbn: "978-0062073488",
|
||||
series: false,
|
||||
},
|
||||
"The Alchemist" => {
|
||||
reading_time: 10_800,
|
||||
isbn: "978-0061122415",
|
||||
series: false,
|
||||
},
|
||||
"The Hitchhiker's Guide to the Galaxy" => {
|
||||
reading_time: 12_600,
|
||||
isbn: "978-0345391803",
|
||||
series: false,
|
||||
},
|
||||
"The Complete works of Shakespeare" => {
|
||||
reading_time: 180_000,
|
||||
isbn: "978-1853268953",
|
||||
series: true,
|
||||
},
|
||||
"The Game of Thrones Series" => {
|
||||
reading_time: 360_000,
|
||||
isbn: "978-0007477159",
|
||||
series: true,
|
||||
},
|
||||
"Malazan Book of the Fallen" => {
|
||||
reading_time: 720_000,
|
||||
isbn: "978-0765348821",
|
||||
series: true,
|
||||
},
|
||||
"Terry Pratchett’s Discworld series" => {
|
||||
reading_time: 1_440_000,
|
||||
isbn: "978-9123684458",
|
||||
series: true,
|
||||
},
|
||||
"The Wandering Inn web series" => {
|
||||
reading_time: 2_160_000,
|
||||
isbn: "the-wandering-inn",
|
||||
series: true,
|
||||
},
|
||||
"The Combined Cosmere works + Wheel of Time" => {
|
||||
reading_time: 2_880_000,
|
||||
isbn: "978-0812511819",
|
||||
series: true,
|
||||
},
|
||||
"The Star Trek novels" => {
|
||||
reading_time: 3_600_000,
|
||||
isbn: "978-1852860691",
|
||||
series: true,
|
||||
},
|
||||
}.symbolize_keys
|
||||
end
|
||||
|
||||
def best_book_fit(reading_time)
|
||||
reading_time_rest = reading_time
|
||||
books = []
|
||||
best_fit =
|
||||
POPULAR_BOOKS
|
||||
.select { |_, v| v[:reading_time] > reading_time }
|
||||
.min_by { |_, v| v[:reading_time] }
|
||||
|
||||
while reading_time_rest > 0
|
||||
best_fit = popular_books.min_by { |_, v| (v[:reading_time] - reading_time_rest).abs }
|
||||
break if best_fit.nil?
|
||||
return if best_fit.nil?
|
||||
|
||||
books << best_fit.first
|
||||
reading_time_rest -= best_fit.last[:reading_time]
|
||||
end
|
||||
|
||||
return if books.empty?
|
||||
|
||||
book_title =
|
||||
books.group_by { |book| book }.transform_values(&:count).max_by { |_, count| count }.first
|
||||
|
||||
{
|
||||
title: book_title,
|
||||
isbn: popular_books[book_title][:isbn],
|
||||
series: popular_books[book_title][:series],
|
||||
}
|
||||
{ title: best_fit.first, isbn: best_fit.last[:isbn], series: best_fit.last[:series] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -10,7 +10,6 @@ module DiscourseRewind
|
||||
{ word: "you", score: 80 },
|
||||
{ word: "overachieved", score: 70 },
|
||||
{ word: "assume", score: 60 },
|
||||
{ word: "week", score: 50 },
|
||||
],
|
||||
identifier: "top-words",
|
||||
}
|
||||
@ -79,7 +78,13 @@ module DiscourseRewind
|
||||
LIMIT 100
|
||||
SQL
|
||||
|
||||
word_score = words.map { { word: _1.original_word, score: _1.ndoc + _1.nentry } }
|
||||
word_score =
|
||||
words
|
||||
.map do |word_data|
|
||||
{ word: word_data.original_word, score: word_data.ndoc + word_data.nentry }
|
||||
end
|
||||
.sort_by! { |w| -w[:score] }
|
||||
.take(5)
|
||||
|
||||
{ data: word_score, identifier: "top-words" }
|
||||
end
|
||||
|
||||
@ -5,7 +5,8 @@ module DiscourseRewind
|
||||
#
|
||||
# @example
|
||||
# ::DiscourseRewind::Rewind::Fetch.call(
|
||||
# guardian: guardian
|
||||
# guardian: guardian,
|
||||
# params: { year: 2023, username: 'codinghorror' }
|
||||
# )
|
||||
#
|
||||
class FetchReports
|
||||
|
||||
@ -28,6 +28,32 @@ export default class ActivityCalendar extends Component {
|
||||
return moment().month(monthIndex).format("MMM");
|
||||
}
|
||||
|
||||
@action
|
||||
computeCellTitle(cell) {
|
||||
if (!cell || !cell.date) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const date = moment(cell.date).format("LL");
|
||||
|
||||
if (cell.visited && cell.post_count === 0) {
|
||||
return i18n(
|
||||
"discourse_rewind.reports.activity_calendar.cell_title.visited_no_posts",
|
||||
{ date }
|
||||
);
|
||||
} else if (cell.post_count > 0) {
|
||||
return i18n(
|
||||
"discourse_rewind.reports.activity_calendar.cell_title.visited_with_posts",
|
||||
{ date, count: cell.post_count }
|
||||
);
|
||||
}
|
||||
|
||||
return i18n(
|
||||
"discourse_rewind.reports.activity_calendar.cell_title.no_activity",
|
||||
{ date }
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
computeClass(count) {
|
||||
if (!count) {
|
||||
@ -107,7 +133,7 @@ export default class ActivityCalendar extends Component {
|
||||
{{#each row as |cell|}}
|
||||
<td
|
||||
data-date={{cell.date}}
|
||||
title={{cell.date}}
|
||||
title={{this.computeCellTitle cell}}
|
||||
class={{concatClass
|
||||
"rewind-calendar-cell"
|
||||
(this.computeClass cell.post_count)
|
||||
|
||||
@ -1,24 +1,26 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { concat } from "@ember/helper";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import concatClass from "discourse/helpers/concat-class";
|
||||
import icon from "discourse/helpers/d-icon";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class BestPosts extends Component {
|
||||
rank(idx) {
|
||||
return idx + 1;
|
||||
rankClass(idx) {
|
||||
return `rank-${idx + 1}`;
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if @report.data.length}}
|
||||
<div class="rewind-report-page -best-posts">
|
||||
<h2 class="rewind-report-title">{{i18n
|
||||
<h2 class="rewind-report-title">
|
||||
{{i18n
|
||||
"discourse_rewind.reports.best_posts.title"
|
||||
count=@report.data.length
|
||||
}}</h2>
|
||||
}}
|
||||
</h2>
|
||||
<div class="rewind-report-container">
|
||||
{{#each @report.data as |post idx|}}
|
||||
<div class={{concat "rewind-card" " rank-" (this.rank idx)}}>
|
||||
<div class={{concatClass "rewind-card" (this.rankClass idx)}}>
|
||||
<span class="best-posts -rank"></span>
|
||||
<span class="best-posts -rank"></span>
|
||||
<div class="best-posts__post"><p>{{htmlSafe
|
||||
|
||||
@ -1,27 +1,31 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { concat } from "@ember/helper";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import concatClass from "discourse/helpers/concat-class";
|
||||
import replaceEmoji from "discourse/helpers/replace-emoji";
|
||||
import getURL from "discourse/lib/get-url";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class BestTopics extends Component {
|
||||
rank(idx) {
|
||||
return idx + 1;
|
||||
rankClass(idx) {
|
||||
return `rank-${idx + 1}`;
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if @report.data.length}}
|
||||
<div class="rewind-report-page -best-topics">
|
||||
<h2 class="rewind-report-title">{{i18n
|
||||
<h2 class="rewind-report-title">
|
||||
{{i18n
|
||||
"discourse_rewind.reports.best_topics.title"
|
||||
count=@report.data.length
|
||||
}}</h2>
|
||||
}}
|
||||
</h2>
|
||||
<div class="rewind-report-container">
|
||||
<div class="rewind-card">
|
||||
{{#each @report.data as |topic idx|}}
|
||||
<a
|
||||
href={{concat "/t/-/" topic.topic_id}}
|
||||
class={{concat "best-topics__topic" " rank-" (this.rank idx)}}
|
||||
href={{getURL (concat "/t/-/" topic.topic_id)}}
|
||||
class={{concatClass "best-topics__topic" (this.rankClass idx)}}
|
||||
>
|
||||
<span class="best-topics -rank"></span>
|
||||
<span class="best-topics -rank"></span>
|
||||
|
||||
@ -1,28 +1,23 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import WordCard from "discourse/plugins/discourse-rewind/discourse/components/reports/top-words/word-card";
|
||||
|
||||
export default class WordCards extends Component {
|
||||
get topWords() {
|
||||
return this.args.report.data.sort((a, b) => b.score - a.score).slice(0, 5);
|
||||
}
|
||||
|
||||
<template>
|
||||
<div class="rewind-report-page -top-words">
|
||||
<div class="rewind-report-container">
|
||||
<h2 class="rewind-report-title">{{i18n
|
||||
"discourse_rewind.reports.top_words.title"
|
||||
}}</h2>
|
||||
<div class="cards-container">
|
||||
{{#each this.topWords as |entry index|}}
|
||||
<WordCard
|
||||
@word={{entry.word}}
|
||||
@count={{entry.score}}
|
||||
@index={{index}}
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
const WordCards = <template>
|
||||
<div class="rewind-report-page -top-words">
|
||||
<div class="rewind-report-container">
|
||||
<h2 class="rewind-report-title">{{i18n
|
||||
"discourse_rewind.reports.top_words.title"
|
||||
}}</h2>
|
||||
<div class="cards-container">
|
||||
{{#each @report.data as |entry index|}}
|
||||
<WordCard
|
||||
@word={{entry.word}}
|
||||
@count={{entry.score}}
|
||||
@index={{index}}
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
}
|
||||
</div>
|
||||
</template>;
|
||||
|
||||
export default WordCards;
|
||||
|
||||
@ -13,6 +13,12 @@ en:
|
||||
reports:
|
||||
activity_calendar:
|
||||
title: Activity Calendar
|
||||
cell_title:
|
||||
visited_no_posts: "You visited and lurked on %{date}, but made no posts."
|
||||
visited_with_posts:
|
||||
one: "You made %{count} post on %{date}."
|
||||
other: "You made %{count} posts on %{date}."
|
||||
no_activity: "You weren't around on %{date}."
|
||||
top_words:
|
||||
title: Word Usage
|
||||
reading_time:
|
||||
|
||||
68
spec/actions/activity_calendar_spec.rb
Normal file
68
spec/actions/activity_calendar_spec.rb
Normal file
@ -0,0 +1,68 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe DiscourseRewind::Action::ActivityCalendar do
|
||||
fab!(:date) { Date.new(2021).all_year }
|
||||
fab!(:user)
|
||||
fab!(:other_user, :user)
|
||||
|
||||
fab!(:post_1) { Fabricate(:post, user: user, created_at: Date.new(2021, 1, 15)) }
|
||||
fab!(:post_2) { Fabricate(:post, user: user, created_at: Date.new(2021, 6, 27)) }
|
||||
fab!(:post_3) { Fabricate(:post, user: user, created_at: Date.new(2021, 6, 27)) }
|
||||
fab!(:post_4) { Fabricate(:post, user: user, created_at: Date.new(2021, 11, 27)) }
|
||||
fab!(:post_5) { Fabricate(:post, user: other_user, created_at: Date.new(2021, 11, 27)) }
|
||||
fab!(:post_6) { Fabricate(:post, user: user, created_at: Date.new(2022, 02, 27)) }
|
||||
|
||||
fab!(:user_visit_1) do
|
||||
UserVisit.create!(
|
||||
user_id: user.id,
|
||||
visited_at: Date.new(2021, 3, 10),
|
||||
posts_read: 5,
|
||||
time_read: 120,
|
||||
)
|
||||
end
|
||||
fab!(:user_visit_2) do
|
||||
UserVisit.create!(
|
||||
user_id: user.id,
|
||||
visited_at: Date.new(2021, 4, 18),
|
||||
posts_read: 12,
|
||||
time_read: 1200,
|
||||
)
|
||||
end
|
||||
fab!(:user_visit_3) do
|
||||
UserVisit.create!(
|
||||
user_id: other_user.id,
|
||||
visited_at: Date.new(2021, 7, 24),
|
||||
posts_read: 12,
|
||||
time_read: 1200,
|
||||
)
|
||||
end
|
||||
|
||||
it "returns an entry for all days of the last year" do
|
||||
result = call_report
|
||||
expect(result[:data].map { |d| d[:date] }.count).to eq(365)
|
||||
end
|
||||
|
||||
it "counts up posts for the user on days they were made in the year" do
|
||||
result = call_report
|
||||
expect(result[:data].find { |d| d[:date] == Date.new(2021, 1, 15) }[:post_count]).to eq(1)
|
||||
expect(result[:data].find { |d| d[:date] == Date.new(2021, 6, 27) }[:post_count]).to eq(2)
|
||||
expect(result[:data].find { |d| d[:date] == Date.new(2021, 11, 27) }[:post_count]).to eq(1)
|
||||
expect(result[:data].find { |d| d[:date] == Date.new(2022, 2, 27) }).to be_nil
|
||||
end
|
||||
|
||||
it "marks dates as visited for the user in the year" do
|
||||
result = call_report
|
||||
expect(result[:data].find { |d| d[:date] == Date.new(2021, 3, 10) }[:visited]).to eq(true)
|
||||
expect(result[:data].find { |d| d[:date] == Date.new(2021, 4, 18) }[:visited]).to eq(true)
|
||||
expect(result[:data].find { |d| d[:date] == Date.new(2021, 5, 1) }[:visited]).to eq(false)
|
||||
end
|
||||
|
||||
context "when a post is deleted" do
|
||||
before { post_1.trash! }
|
||||
|
||||
it "does not count" do
|
||||
result = call_report
|
||||
expect(result[:data].find { |d| d[:date] == Date.new(2021, 1, 15) }[:post_count]).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
66
spec/actions/best_posts_spec.rb
Normal file
66
spec/actions/best_posts_spec.rb
Normal file
@ -0,0 +1,66 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe DiscourseRewind::Action::BestPosts do
|
||||
fab!(:date) { Date.new(2021).all_year }
|
||||
fab!(:user)
|
||||
fab!(:post_1) { Fabricate(:post, created_at: random_datetime, user: user, post_number: 3) }
|
||||
fab!(:post_2) { Fabricate(:post, created_at: random_datetime, user: user, post_number: 2) }
|
||||
fab!(:post_3) { Fabricate(:post, created_at: random_datetime, user: user, post_number: 10) }
|
||||
fab!(:post_4) { Fabricate(:post, created_at: random_datetime, user: user, post_number: 6) }
|
||||
fab!(:post_5) { Fabricate(:post, created_at: random_datetime, user: user, post_number: 1) }
|
||||
|
||||
describe ".call" do
|
||||
it "returns top 3 posts ordered by like count" do
|
||||
post_4.update!(like_count: 15)
|
||||
post_3.update!(like_count: 13)
|
||||
post_1.update!(like_count: 11)
|
||||
post_2.update!(like_count: 9)
|
||||
post_5.update!(like_count: 7)
|
||||
|
||||
expect(call_report[:data]).to eq(
|
||||
[
|
||||
{
|
||||
post_number: post_4.post_number,
|
||||
topic_id: post_4.topic_id,
|
||||
like_count: post_4.like_count,
|
||||
reply_count: post_4.reply_count,
|
||||
excerpt:
|
||||
post_4.excerpt(200, { strip_links: true, remap_emoji: true, keep_images: true }),
|
||||
},
|
||||
{
|
||||
post_number: post_3.post_number,
|
||||
topic_id: post_3.topic_id,
|
||||
like_count: post_3.like_count,
|
||||
reply_count: post_3.reply_count,
|
||||
excerpt:
|
||||
post_3.excerpt(200, { strip_links: true, remap_emoji: true, keep_images: true }),
|
||||
},
|
||||
{
|
||||
post_number: post_1.post_number,
|
||||
topic_id: post_1.topic_id,
|
||||
like_count: post_1.like_count,
|
||||
reply_count: post_1.reply_count,
|
||||
excerpt:
|
||||
post_1.excerpt(200, { strip_links: true, remap_emoji: true, keep_images: true }),
|
||||
},
|
||||
],
|
||||
)
|
||||
end
|
||||
|
||||
context "when a post is deleted" do
|
||||
before { post_1.trash!(Discourse.system_user) }
|
||||
|
||||
it "is not included" do
|
||||
expect(call_report[:data].map { |d| d[:post_number] }).not_to include(post_1.post_number)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a post is made by another user" do
|
||||
before { post_1.update!(user: Fabricate(:user)) }
|
||||
|
||||
it "is not included" do
|
||||
expect(call_report[:data].map { |d| d[:post_number] }).not_to include(post_1.post_number)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
61
spec/actions/best_topics_spec.rb
Normal file
61
spec/actions/best_topics_spec.rb
Normal file
@ -0,0 +1,61 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe DiscourseRewind::Action::BestTopics do
|
||||
fab!(:date) { Date.new(2021).all_year }
|
||||
fab!(:user)
|
||||
fab!(:topic_1) { Fabricate(:topic, user: user, created_at: random_datetime) }
|
||||
fab!(:topic_2) { Fabricate(:topic, user: user, created_at: random_datetime) }
|
||||
fab!(:topic_3) { Fabricate(:topic, user: user, created_at: random_datetime) }
|
||||
fab!(:topic_4) { Fabricate(:topic, user: user, created_at: random_datetime) }
|
||||
fab!(:topic_5) { Fabricate(:topic, user: user, created_at: random_datetime) }
|
||||
|
||||
before { TopTopic.refresh! }
|
||||
|
||||
describe ".call" do
|
||||
it "returns top 3 topics ordered by yearly_score" do
|
||||
TopTopic.find_by(topic_id: topic_1.id).update!(yearly_score: 15)
|
||||
TopTopic.find_by(topic_id: topic_2.id).update!(yearly_score: 10)
|
||||
TopTopic.find_by(topic_id: topic_3.id).update!(yearly_score: 6)
|
||||
TopTopic.find_by(topic_id: topic_4.id).update!(yearly_score: 11)
|
||||
TopTopic.find_by(topic_id: topic_5.id).update!(yearly_score: 13)
|
||||
expect(call_report[:data]).to eq(
|
||||
[
|
||||
{
|
||||
topic_id: topic_1.id,
|
||||
title: topic_1.title,
|
||||
excerpt: topic_1.excerpt,
|
||||
yearly_score: 15,
|
||||
},
|
||||
{
|
||||
topic_id: topic_5.id,
|
||||
title: topic_5.title,
|
||||
excerpt: topic_5.excerpt,
|
||||
yearly_score: 13,
|
||||
},
|
||||
{
|
||||
topic_id: topic_4.id,
|
||||
title: topic_4.title,
|
||||
excerpt: topic_4.excerpt,
|
||||
yearly_score: 11,
|
||||
},
|
||||
],
|
||||
)
|
||||
end
|
||||
|
||||
context "when a topic is deleted" do
|
||||
before { topic_1.trash!(Discourse.system_user) }
|
||||
|
||||
it "is not included" do
|
||||
expect(call_report[:data].map { |d| d[:topic_id] }).not_to include(topic_1.id)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a topic" do
|
||||
before { topic_1.update!(user: Fabricate(:user)) }
|
||||
|
||||
it "is not included" do
|
||||
expect(call_report[:data].map { |d| d[:topic_id] }).not_to include(topic_1.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
92
spec/actions/most_viewed_categories_spec.rb
Normal file
92
spec/actions/most_viewed_categories_spec.rb
Normal file
@ -0,0 +1,92 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe DiscourseRewind::Action::MostViewedCategories do
|
||||
fab!(:date) { Date.new(2021).all_year }
|
||||
fab!(:user)
|
||||
fab!(:other_user, :user)
|
||||
|
||||
fab!(:category_1) { Fabricate(:category, name: "Technology") }
|
||||
fab!(:category_2) { Fabricate(:category, name: "Science") }
|
||||
fab!(:category_3) { Fabricate(:category, name: "Philosophy") }
|
||||
fab!(:category_4) { Fabricate(:category, name: "Literature") }
|
||||
fab!(:category_5) { Fabricate(:category, name: "History") }
|
||||
|
||||
fab!(:topic_1) { Fabricate(:topic, category: category_1) }
|
||||
fab!(:topic_2) { Fabricate(:topic, category: category_1) }
|
||||
fab!(:topic_3) { Fabricate(:topic, category: category_2) }
|
||||
fab!(:topic_4) { Fabricate(:topic, category: category_3) }
|
||||
fab!(:topic_5) { Fabricate(:topic, category: category_4) }
|
||||
fab!(:topic_6) { Fabricate(:topic, category: category_5) }
|
||||
|
||||
describe ".call" do
|
||||
it "returns top 4 most viewed categories ordered by view count" do
|
||||
# Category 1: 2 views
|
||||
TopicViewItem.add(topic_1.id, "127.0.0.1", user.id, Date.new(2021, 3, 15))
|
||||
TopicViewItem.add(topic_2.id, "127.0.0.2", user.id, Date.new(2021, 4, 20))
|
||||
|
||||
# Category 2: 1 view
|
||||
TopicViewItem.add(topic_3.id, "127.0.0.3", user.id, Date.new(2021, 5, 10))
|
||||
|
||||
# Category 3: 1 view
|
||||
TopicViewItem.add(topic_4.id, "127.0.0.4", user.id, Date.new(2021, 6, 5))
|
||||
|
||||
# Category 4: 3 views (same topic, multiple views)
|
||||
TopicViewItem.add(topic_5.id, "127.0.0.5", user.id, Date.new(2021, 7, 1))
|
||||
TopicViewItem.add(topic_5.id, "127.0.0.6", user.id, Date.new(2021, 8, 15))
|
||||
TopicViewItem.add(topic_5.id, "127.0.0.7", user.id, Date.new(2021, 9, 20))
|
||||
|
||||
# Category 5: 0 views
|
||||
|
||||
result = call_report
|
||||
|
||||
expect(result[:identifier]).to eq("most-viewed-categories")
|
||||
expect(result[:data].length).to eq(4)
|
||||
expect(result[:data]).to eq(
|
||||
[
|
||||
{ category_id: category_1.id, name: "Technology" },
|
||||
{ category_id: category_2.id, name: "Science" },
|
||||
{ category_id: category_3.id, name: "Philosophy" },
|
||||
{ category_id: category_4.id, name: "Literature" },
|
||||
],
|
||||
)
|
||||
end
|
||||
|
||||
it "only includes categories the user can see (no read-restricted/private categories)" do
|
||||
group = Fabricate(:group)
|
||||
private_category = Fabricate(:private_category, group: group)
|
||||
private_topic = Fabricate(:topic, category: private_category)
|
||||
|
||||
TopicViewItem.add(private_topic.id, "127.0.0.1", user.id, Date.new(2021, 3, 15))
|
||||
|
||||
result = call_report
|
||||
expect(result[:data].map { |c| c[:category_id] }).not_to include(private_category.id)
|
||||
end
|
||||
|
||||
it "filters by date range" do
|
||||
TopicViewItem.add(topic_1.id, "127.0.0.1", user.id, Date.new(2021, 3, 15))
|
||||
TopicViewItem.add(topic_2.id, "127.0.0.2", user.id, Date.new(2020, 12, 31))
|
||||
|
||||
result = call_report
|
||||
|
||||
expect(result[:data].length).to eq(1)
|
||||
expect(result[:data].first[:category_id]).to eq(category_1.id)
|
||||
end
|
||||
|
||||
it "only counts views for the specific user" do
|
||||
TopicViewItem.add(topic_1.id, "127.0.0.1", user.id, Date.new(2021, 3, 15))
|
||||
TopicViewItem.add(topic_2.id, "127.0.0.2", other_user.id, Date.new(2021, 4, 20))
|
||||
|
||||
result = call_report
|
||||
|
||||
expect(result[:data].length).to eq(1)
|
||||
expect(result[:data].first[:category_id]).to eq(category_1.id)
|
||||
end
|
||||
|
||||
it "returns empty array when no views" do
|
||||
result = call_report
|
||||
|
||||
expect(result[:identifier]).to eq("most-viewed-categories")
|
||||
expect(result[:data]).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
117
spec/actions/most_viewed_tags_spec.rb
Normal file
117
spec/actions/most_viewed_tags_spec.rb
Normal file
@ -0,0 +1,117 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe DiscourseRewind::Action::MostViewedTags do
|
||||
fab!(:date) { Date.new(2021).all_year }
|
||||
fab!(:user)
|
||||
fab!(:other_user, :user)
|
||||
|
||||
fab!(:tag_1) { Fabricate(:tag, name: "ruby") }
|
||||
fab!(:tag_2) { Fabricate(:tag, name: "javascript") }
|
||||
fab!(:tag_3) { Fabricate(:tag, name: "python") }
|
||||
fab!(:tag_4) { Fabricate(:tag, name: "golang") }
|
||||
fab!(:tag_5) { Fabricate(:tag, name: "rust") }
|
||||
|
||||
fab!(:topic_1, :topic)
|
||||
fab!(:topic_2, :topic)
|
||||
fab!(:topic_3, :topic)
|
||||
fab!(:topic_4, :topic)
|
||||
fab!(:topic_5, :topic)
|
||||
|
||||
before do
|
||||
SiteSetting.tagging_enabled = true
|
||||
|
||||
topic_1.tags = [tag_1]
|
||||
topic_2.tags = [tag_1]
|
||||
topic_3.tags = [tag_2]
|
||||
topic_4.tags = [tag_3]
|
||||
topic_5.tags = [tag_4]
|
||||
end
|
||||
|
||||
describe ".call" do
|
||||
it "returns top 4 most viewed tags ordered by view count" do
|
||||
# Tag 1 (ruby): 2 views (2 different topics)
|
||||
TopicViewItem.add(topic_1.id, "127.0.0.1", user.id, Date.new(2021, 3, 15))
|
||||
TopicViewItem.add(topic_2.id, "127.0.0.2", user.id, Date.new(2021, 4, 20))
|
||||
|
||||
# Tag 2 (javascript): 1 view
|
||||
TopicViewItem.add(topic_3.id, "127.0.0.3", user.id, Date.new(2021, 5, 10))
|
||||
|
||||
# Tag 3 (python): 1 view
|
||||
TopicViewItem.add(topic_4.id, "127.0.0.4", user.id, Date.new(2021, 6, 5))
|
||||
|
||||
# Tag 4 (golang): 3 views (same topic, multiple views)
|
||||
TopicViewItem.add(topic_5.id, "127.0.0.5", user.id, Date.new(2021, 7, 1))
|
||||
TopicViewItem.add(topic_5.id, "127.0.0.6", user.id, Date.new(2021, 8, 15))
|
||||
TopicViewItem.add(topic_5.id, "127.0.0.7", user.id, Date.new(2021, 9, 20))
|
||||
|
||||
# Tag 5 (rust): 0 views
|
||||
|
||||
result = call_report
|
||||
|
||||
expect(result[:data]).to eq(
|
||||
[
|
||||
{ tag_id: tag_1.id, name: "ruby" },
|
||||
{ tag_id: tag_2.id, name: "javascript" },
|
||||
{ tag_id: tag_3.id, name: "python" },
|
||||
{ tag_id: tag_4.id, name: "golang" },
|
||||
],
|
||||
)
|
||||
end
|
||||
|
||||
it "only includes tags the user can see (no restricted tags)" do
|
||||
group = Fabricate(:group)
|
||||
tag_group = Fabricate(:tag_group, tags: [tag_5])
|
||||
tag_group.permissions = { group.name => TagGroupPermission.permission_types[:full] }
|
||||
tag_group.save!
|
||||
|
||||
restricted_topic = Fabricate(:topic)
|
||||
restricted_topic.tags = [tag_5]
|
||||
|
||||
TopicViewItem.add(restricted_topic.id, "127.0.0.1", user.id, Date.new(2021, 3, 15))
|
||||
|
||||
result = call_report
|
||||
expect(result[:data].map { |t| t[:tag_id] }).not_to include(tag_5.id)
|
||||
end
|
||||
|
||||
it "filters by date range" do
|
||||
TopicViewItem.add(topic_1.id, "127.0.0.1", user.id, Date.new(2021, 3, 15))
|
||||
TopicViewItem.add(topic_2.id, "127.0.0.2", user.id, Date.new(2020, 12, 31))
|
||||
|
||||
result = call_report
|
||||
|
||||
expect(result[:data].length).to eq(1)
|
||||
expect(result[:data].first[:tag_id]).to eq(tag_1.id)
|
||||
end
|
||||
|
||||
it "only counts views for the specific user" do
|
||||
TopicViewItem.add(topic_1.id, "127.0.0.1", user.id, Date.new(2021, 3, 15))
|
||||
TopicViewItem.add(topic_2.id, "127.0.0.2", other_user.id, Date.new(2021, 4, 20))
|
||||
|
||||
result = call_report
|
||||
|
||||
expect(result[:data].length).to eq(1)
|
||||
expect(result[:data].first[:tag_id]).to eq(tag_1.id)
|
||||
end
|
||||
|
||||
it "counts distinct topics per tag" do
|
||||
multi_tag_topic = Fabricate(:topic)
|
||||
multi_tag_topic.tags = [tag_1, tag_2]
|
||||
|
||||
TopicViewItem.add(multi_tag_topic.id, "127.0.0.1", user.id, Date.new(2021, 3, 15))
|
||||
TopicViewItem.add(multi_tag_topic.id, "127.0.0.2", user.id, Date.new(2021, 4, 20))
|
||||
|
||||
result = call_report
|
||||
|
||||
tag_1_data = result[:data].find { |t| t[:tag_id] == tag_1.id }
|
||||
tag_2_data = result[:data].find { |t| t[:tag_id] == tag_2.id }
|
||||
expect(tag_1_data).not_to be_nil
|
||||
expect(tag_2_data).not_to be_nil
|
||||
end
|
||||
|
||||
it "returns empty array when no views" do
|
||||
result = call_report
|
||||
|
||||
expect(result[:data]).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
146
spec/actions/reading_time_spec.rb
Normal file
146
spec/actions/reading_time_spec.rb
Normal file
@ -0,0 +1,146 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe DiscourseRewind::Action::ReadingTime do
|
||||
fab!(:date) { Date.new(2021).all_year }
|
||||
fab!(:user)
|
||||
fab!(:other_user, :user)
|
||||
|
||||
fab!(:user_visit_1) do
|
||||
UserVisit.create!(
|
||||
user_id: user.id,
|
||||
visited_at: Date.new(2021, 3, 10),
|
||||
posts_read: 5,
|
||||
time_read: 100,
|
||||
)
|
||||
end
|
||||
fab!(:user_visit_2) do
|
||||
UserVisit.create!(
|
||||
user_id: user.id,
|
||||
visited_at: Date.new(2021, 4, 18),
|
||||
posts_read: 12,
|
||||
time_read: 1000,
|
||||
)
|
||||
end
|
||||
fab!(:user_visit_3) do
|
||||
UserVisit.create!(
|
||||
user_id: other_user.id,
|
||||
visited_at: Date.new(2021, 7, 24),
|
||||
posts_read: 8,
|
||||
time_read: 1200,
|
||||
)
|
||||
end
|
||||
|
||||
def new_target_time_read(value)
|
||||
value - 1000
|
||||
end
|
||||
|
||||
it "calculates reading time for the year correctly" do
|
||||
result = call_report
|
||||
expect(result[:data][:reading_time]).to eq(1100)
|
||||
end
|
||||
|
||||
it "matches the correct book based on reading time" do
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("The Metamorphosis")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(5300))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("The Little Prince")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(7100))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("Animal Farm")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(10_700))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("The Alchemist")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(12_500))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("The Great Gatsby")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(14_900))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("Fahrenheit 451")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(16_100))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("And Then There Were None")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(16_700))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("1984")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(17_900))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("The Catcher in the Rye")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(19_640))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("The Hunger Games")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(22_700))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("To Kill a Mockingbird")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(24_500))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("Harry Potter and the Sorcerer's Stone")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(25_100))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("Pride and Prejudice")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(26_900))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("The Hobbit")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(29_900))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("Little Women")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(34_100))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("Jane Eyre")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(37_700))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("The Da Vinci Code")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(46_700))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("One Hundred Years of Solitude")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(107_900))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("The Lord of the Rings")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(179_900))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("The Complete works of Shakespeare")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(359_900))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("The Game of Thrones Series")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(719_900))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("Malazan Book of the Fallen")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(1_439_900))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("Terry Pratchett's Discworld series")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(2_159_900))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("The Wandering Inn web series")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(2_879_900))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("The Combined Cosmere works + Wheel of Time")
|
||||
|
||||
user_visit_1.update!(time_read: new_target_time_read(3_599_900))
|
||||
result = call_report
|
||||
expect(result[:data][:book]).to eq("The Star Trek novels")
|
||||
end
|
||||
end
|
||||
135
spec/actions/top_words_spec.rb
Normal file
135
spec/actions/top_words_spec.rb
Normal file
@ -0,0 +1,135 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe DiscourseRewind::Action::TopWords do
|
||||
fab!(:date) { Date.new(2021).all_year }
|
||||
fab!(:user)
|
||||
fab!(:other_user, :user)
|
||||
|
||||
fab!(:post1) do
|
||||
Fabricate(
|
||||
:post,
|
||||
user: user,
|
||||
raw: "apple orange banana apple apple orange",
|
||||
created_at: random_datetime,
|
||||
)
|
||||
end
|
||||
fab!(:post2) do
|
||||
Fabricate(:post, user: user, raw: "cucumber tomato banana orange", created_at: random_datetime)
|
||||
end
|
||||
fab!(:post3) do
|
||||
Fabricate(:post, user: user, raw: "grape watermelon mango", created_at: random_datetime)
|
||||
end
|
||||
fab!(:post4) do
|
||||
Fabricate(:post, user: user, raw: "apple banana grape apple", created_at: random_datetime)
|
||||
end
|
||||
fab!(:post5) do
|
||||
Fabricate(:post, user: user, raw: "apple orange apple apple", created_at: random_datetime)
|
||||
end
|
||||
fab!(:other_user_post) do
|
||||
Fabricate(:post, user: other_user, raw: "apple apple apple", created_at: random_datetime)
|
||||
end
|
||||
|
||||
before do
|
||||
SearchIndexer.enable
|
||||
[post1, post2, post3, post4, post5, other_user_post].each do |post|
|
||||
SearchIndexer.index(post, force: true)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".call" do
|
||||
it "limits top words to 5" do
|
||||
result = call_report
|
||||
|
||||
expect(result[:data].length).to eq(5)
|
||||
end
|
||||
|
||||
it "returns top words ordered by frequency" do
|
||||
result = call_report
|
||||
|
||||
expect(result[:identifier]).to eq("top-words")
|
||||
|
||||
words = result[:data]
|
||||
|
||||
expect(words.first[:word]).to eq("apple")
|
||||
expect(words.second[:word]).to eq("orange")
|
||||
expect(words.third[:word]).to eq("banana")
|
||||
|
||||
expect(words.map { |w| w[:word] }).to include("apple", "orange", "banana", "grape")
|
||||
expect(words.map { |w| w[:score] }).to eq(words.map { |w| w[:score] }.sort.reverse)
|
||||
end
|
||||
|
||||
context "when a post is deleted" do
|
||||
before do
|
||||
post1.trash!(Discourse.system_user)
|
||||
post1.post_search_data.destroy!
|
||||
end
|
||||
|
||||
it "does not include words from deleted posts" do
|
||||
result = call_report
|
||||
|
||||
words = result[:data]
|
||||
|
||||
apple = words.find { |w| w[:word] == "apple" }
|
||||
expect(apple[:score]).to be < 9
|
||||
end
|
||||
end
|
||||
|
||||
context "when posts are from another user" do
|
||||
it "does not include words from other users' posts" do
|
||||
result = call_report
|
||||
|
||||
words = result[:data]
|
||||
apple_score = words.find { |w| w[:word] == "apple" }[:score]
|
||||
|
||||
expect(apple_score).to be < 12
|
||||
end
|
||||
end
|
||||
|
||||
context "with a large number of posts and words" do
|
||||
before do
|
||||
# Create posts with different frequencies of non-stop words
|
||||
10.times do |i|
|
||||
post =
|
||||
Fabricate(
|
||||
:post,
|
||||
user: user,
|
||||
raw: "#{frequent_word} #{frequent_word} #{frequent_word} #{infrequent_word}",
|
||||
created_at: random_datetime,
|
||||
)
|
||||
SearchIndexer.index(post, force: true)
|
||||
end
|
||||
end
|
||||
|
||||
let(:frequent_word) { "zucchini" }
|
||||
let(:infrequent_word) { "xylophone" }
|
||||
|
||||
it "ranks high frequency words higher than low frequency words" do
|
||||
result = call_report
|
||||
|
||||
words = result[:data]
|
||||
frequent_word_entry = words.find { |w| w[:word] == frequent_word }
|
||||
infrequent_word_entry = words.find { |w| w[:word] == infrequent_word }
|
||||
|
||||
expect(frequent_word_entry).to be_present
|
||||
expect(infrequent_word_entry).to be_present
|
||||
|
||||
expect(frequent_word_entry[:score]).to be > infrequent_word_entry[:score]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when in rails development mode" do
|
||||
before { Rails.env.stubs(:development?).returns(true) }
|
||||
|
||||
it "returns fake data" do
|
||||
result = call_report
|
||||
|
||||
expect(result[:identifier]).to eq("top-words")
|
||||
expect(result[:data].length).to eq(5)
|
||||
expect(result[:data].first[:word]).to eq("seven")
|
||||
expect(result[:data].first[:score]).to eq(100)
|
||||
expect(result[:data].second[:word]).to eq("longest")
|
||||
expect(result[:data].second[:score]).to eq(90)
|
||||
end
|
||||
end
|
||||
end
|
||||
15
spec/plugin_helper.rb
Normal file
15
spec/plugin_helper.rb
Normal file
@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseRewindSpecHelper
|
||||
def call_report
|
||||
# user + date should be defined via fab! in the spec
|
||||
described_class.call(user:, date:, guardian: user.guardian)
|
||||
end
|
||||
|
||||
def random_datetime
|
||||
# date should be defined via fab! in the spec
|
||||
date.to_a.sample.to_datetime + rand(0..23).hours + rand(0..59).minutes + rand(0..59).seconds
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.configure { |config| config.include DiscourseRewindSpecHelper }
|
||||
@ -31,6 +31,17 @@ RSpec.describe(DiscourseRewind::FetchReports) do
|
||||
it { is_expected.to fail_to_find_a_model(:year) }
|
||||
end
|
||||
|
||||
context "in development mode" do
|
||||
before do
|
||||
Rails.env.stubs(:development?).returns(true)
|
||||
freeze_time DateTime.parse("2021-06-22")
|
||||
end
|
||||
|
||||
it "finds the year no matter what month" do
|
||||
expect(result.year).to eq(2021)
|
||||
end
|
||||
end
|
||||
|
||||
context "when reports is cached" do
|
||||
before { freeze_time DateTime.parse("2021-12-22") }
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user