forked from cmccabe/linkulator2
Further work - can now view categories, links and posts using keyboard
0-9
This commit is contained in:
parent
8fe9af529b
commit
1656c0fc7b
29
data.py
29
data.py
|
@ -176,6 +176,7 @@ class LinkData:
|
|||
|
||||
def generate_category_data(self):
|
||||
"""generate categories list and category count from sorted link data"""
|
||||
# TODO: add unread status bool to this query's results
|
||||
self.categories.clear()
|
||||
for record in self.link_data:
|
||||
name = record[4]
|
||||
|
@ -239,7 +240,7 @@ class LinkData:
|
|||
|
||||
return sorted(search_results, key=lambda x: x[0], reverse=True)
|
||||
|
||||
def list_category_details(self, selected_category: str) -> list:
|
||||
def get_links_by_category_name(self, category_name: str) -> list:
|
||||
"""accepts a category name. returns a sorted list of posts belonging to
|
||||
the specified category"""
|
||||
|
||||
|
@ -247,8 +248,8 @@ class LinkData:
|
|||
|
||||
for record in self.link_data:
|
||||
category = record[4]
|
||||
if category == selected_category:
|
||||
postid = record[0]
|
||||
if category == category_name:
|
||||
post_id = record[0]
|
||||
userid = record[1]
|
||||
timestamp = record[2]
|
||||
parent_id = userid + "+" + str(timestamp)
|
||||
|
@ -272,7 +273,7 @@ class LinkData:
|
|||
|
||||
links.append(
|
||||
{
|
||||
"postid": postid,
|
||||
"post_id": post_id,
|
||||
"link_timestamp": timestamp,
|
||||
"link_author": userid,
|
||||
"reply_count": len(replies),
|
||||
|
@ -283,23 +284,23 @@ class LinkData:
|
|||
)
|
||||
return sorted(links, key=lambda x: x["last_modified_timestamp"], reverse=True)
|
||||
|
||||
def get_thread_details(self, selected_thread) -> dict:
|
||||
def get_post(self, post_id) -> dict:
|
||||
output = {}
|
||||
for line in self.link_data:
|
||||
if line[0] == selected_thread:
|
||||
output["parent_id"] = "{}+{}".format(line[1], str(line[2]))
|
||||
output["author"] = line[1]
|
||||
output["date"] = datetime.fromtimestamp(float(line[2])).strftime("%c")
|
||||
output["category"] = line[4]
|
||||
output["url"] = line[5]
|
||||
output["title"] = line[6]
|
||||
for record in self.link_data:
|
||||
if record[0] == post_id:
|
||||
output["parent_id"] = "{}+{}".format(record[1], str(record[2]))
|
||||
output["author"] = record[1]
|
||||
output["timestamp"] = record[2]
|
||||
output["category"] = record[4]
|
||||
output["url"] = record[5]
|
||||
output["title"] = record[6]
|
||||
break
|
||||
|
||||
if not output["parent_id"]:
|
||||
raise ValueError("Sorry, no thread found with that ID.")
|
||||
|
||||
output["replies"] = sorted(
|
||||
[line for line in self.link_data if line[3] == output["parent_id"]],
|
||||
[record for record in self.link_data if record[3] == output["parent_id"]],
|
||||
key=lambda x: x[2],
|
||||
)
|
||||
|
||||
|
|
393
linkulator.py
393
linkulator.py
|
@ -14,7 +14,7 @@ from time import time
|
|||
from urllib.parse import urlparse
|
||||
from datetime import datetime
|
||||
from shutil import which, get_terminal_size
|
||||
from math import ceil
|
||||
from typing import Tuple
|
||||
import curses
|
||||
import locale
|
||||
|
||||
|
@ -36,128 +36,117 @@ def print_page_count(pages):
|
|||
print("Page {} of {}".format(pages.current, pages.count))
|
||||
|
||||
|
||||
def print_categories(categories, cols) -> tuple[str]:
|
||||
"""Prints the list of categories with an indicator for new activity"""
|
||||
output: list[str] = []
|
||||
def print_categories(categories, cols) -> Tuple[str, list[str]]:
|
||||
"""Produces categories screen display data. Returns as tuple of header and
|
||||
content."""
|
||||
|
||||
output.append("{:>4s} New {}".format("ID#", "Category"))
|
||||
thead = "{:>4s} New {}".format("ID#", "Category")
|
||||
|
||||
content: list[str] = []
|
||||
newmarker = "*"
|
||||
namelen = cols - 11
|
||||
name_cols = cols - 11
|
||||
|
||||
for i, record in enumerate(categories):
|
||||
index_number = i + 1
|
||||
name = textwrap.shorten(record["name"], width=namelen, placeholder="...")
|
||||
|
||||
output.append(
|
||||
name = textwrap.shorten(record["name"], width=name_cols, placeholder="...")
|
||||
newmarker = "*" if record["last_updated"] >= config.USER.lastlogin else " "
|
||||
content.append(
|
||||
"{:4d} {} {} ({})".format(
|
||||
index_number,
|
||||
newmarker if record["last_updated"] >= config.USER.lastlogin else " ",
|
||||
newmarker,
|
||||
name,
|
||||
record["count"],
|
||||
)
|
||||
)
|
||||
|
||||
if len(output) < 2:
|
||||
output = "\n There are no posts yet - enter p to post a new link\n".splitlines()
|
||||
return output
|
||||
if not content:
|
||||
thead = ""
|
||||
content = "\nThere are no posts yet - enter p to post a new link\n".splitlines()
|
||||
return thead, content
|
||||
|
||||
|
||||
def print_category_details(category_details, pages):
|
||||
"""produces category detail data, prints it to the console. returns dict
|
||||
containing an index of threads"""
|
||||
view_cat = "category name"
|
||||
columns, _ = get_terminal_size()
|
||||
maxnamelen = len(max(link_data, key=lambda x: len(x[1]))[1])
|
||||
namelen = max(maxnamelen, 6) # minimum field width is 6
|
||||
desclen = (
|
||||
columns - 18 - namelen - 9 - 1
|
||||
) # length of available space for the description field.
|
||||
# The terminal width, minus the width of ID and Date fields and padding,
|
||||
# minus the max name length, minus Resp field and padding width, minus the
|
||||
# unread mark width
|
||||
def print_links(links, cols) -> Tuple[str, list[str]]:
|
||||
"""Produces links screen display data. Accepts links list and max column
|
||||
width. Returns a tuple of header and content."""
|
||||
|
||||
header = "\n{}\n\n {:>3s} {:>10s} {:<{namelen}s} {:<5} {:<s}".format(
|
||||
style_text(view_cat.upper(), False, "bold"),
|
||||
max_author_cols = max([len(link["link_author"]) for link in links])
|
||||
author_cols = max(max_author_cols, 6) # minimum field width is 6
|
||||
description_cols = cols - 18 - author_cols - 9 - 1
|
||||
# description_cols calulated as: the terminal width, minus the width of ID
|
||||
# and Date fields and padding, minus the max name length, minus Resp field
|
||||
# and padding width, minus the unread mark width
|
||||
|
||||
thead = " {:>3s} {:>10s} {:<{author_cols}s} {:<5} {:<s}".format(
|
||||
"ID#",
|
||||
"DATE",
|
||||
"AUTHOR",
|
||||
"#RESP",
|
||||
"DESC",
|
||||
namelen=namelen,
|
||||
"Date",
|
||||
"Author",
|
||||
"#Repl",
|
||||
"Description",
|
||||
author_cols=author_cols,
|
||||
)
|
||||
out = ""
|
||||
|
||||
for i, link in enumerate(category_details):
|
||||
desc = textwrap.shorten(link["description"], width=desclen, placeholder="...")
|
||||
newmarker = (
|
||||
"*" if link["last_modified_timestamp"] >= config.USER.lastlogin else ""
|
||||
content: list[str] = []
|
||||
|
||||
for i, link in enumerate(links):
|
||||
description = textwrap.shorten(
|
||||
link["description"], width=description_cols, placeholder="..."
|
||||
)
|
||||
_dt = datetime.fromtimestamp(float(link["link_timestamp"])).strftime("%Y-%m-%d")
|
||||
out += " {:3d} {:>10s} {:<{namelen}s} [{:3d}] {:s}{}\n".format(
|
||||
i + 1,
|
||||
_dt,
|
||||
link["link_author"],
|
||||
link["reply_count"],
|
||||
desc,
|
||||
newmarker,
|
||||
namelen=namelen,
|
||||
newmarker = "*" if link["has_new_replies"] else ""
|
||||
date = datetime.fromtimestamp(float(link["link_timestamp"])).strftime(
|
||||
"%Y-%m-%d"
|
||||
)
|
||||
|
||||
if len(out) > 0:
|
||||
print(header)
|
||||
print("." * len(header))
|
||||
print(out)
|
||||
print_page_count(pages)
|
||||
else:
|
||||
print("\n\nThere are no posts for this category\n")
|
||||
content.append(
|
||||
" {:3d} {:>10s} {:<{author_cols}s} [{:3d}] {:s}{}".format(
|
||||
i + 1,
|
||||
date,
|
||||
link["link_author"],
|
||||
link["reply_count"],
|
||||
description,
|
||||
newmarker,
|
||||
author_cols=author_cols,
|
||||
)
|
||||
)
|
||||
|
||||
return thead, content
|
||||
|
||||
|
||||
def print_thread_details(thread_details, pages) -> tuple:
|
||||
"""produces thread detail data, prints it to the console"""
|
||||
def print_post(post, cols) -> list[str]:
|
||||
"""Produces post screen display data. Accepts post id and max column
|
||||
width. Returns content list."""
|
||||
|
||||
output: list = []
|
||||
|
||||
# set up line wrapping
|
||||
columns, _ = get_terminal_size()
|
||||
line_wrapper = textwrap.TextWrapper(
|
||||
initial_indent=" " * 2, subsequent_indent=" " * 21, width=columns
|
||||
initial_indent=" " * 2, subsequent_indent=" " * 21, width=cols
|
||||
)
|
||||
|
||||
# post detail view
|
||||
print(
|
||||
"\n\n{:<17}: {}".format(
|
||||
style_text("Title", False, "bold"), thread_details["title"]
|
||||
)
|
||||
)
|
||||
print("{:<17}: {}".format(style_text("Link", False, "bold"), thread_details["url"]))
|
||||
print(
|
||||
"{:<17}: {}".format(
|
||||
style_text("Category", False, "bold"), thread_details["category"]
|
||||
)
|
||||
)
|
||||
print(
|
||||
"{:<17}: {}".format(style_text("User", False, "bold"), thread_details["author"])
|
||||
)
|
||||
print(
|
||||
"{:<17}: {}".format(style_text("Date", False, "bold"), thread_details["date"])
|
||||
)
|
||||
output.append(" {:<12}: {}".format("Title", post["title"]))
|
||||
output.append(" {:<12}: {}".format("Link", post["url"]))
|
||||
output.append(" {:<12}: {}".format("Category", post["category"]))
|
||||
output.append(" {:<12}: {}".format("User", post["author"]))
|
||||
date = datetime.fromtimestamp(float(post["timestamp"])).strftime("%c")
|
||||
output.append(" {:<12}: {}".format("Date", date))
|
||||
|
||||
# post reply view
|
||||
if thread_details["replies"]:
|
||||
print("\n{}:\n".format(style_text("Replies", False, "underline")))
|
||||
for line in thread_details["replies"]:
|
||||
comment_author = line[1]
|
||||
comment_date = datetime.fromtimestamp(float(line[2])).isoformat(
|
||||
if post["replies"]:
|
||||
output.append("\n {}:\n".format("Replies"))
|
||||
for replies in post["replies"]:
|
||||
reply_author = replies[1]
|
||||
reply_date = datetime.fromtimestamp(float(replies[2])).isoformat(
|
||||
sep=" ", timespec="minutes"
|
||||
)
|
||||
comment = line[6]
|
||||
print(
|
||||
reply_text = replies[6]
|
||||
output.append(
|
||||
line_wrapper.fill(
|
||||
"{} {}: {}".format(comment_date, comment_author, comment)
|
||||
" {} {}: {}".format(reply_date, reply_author, reply_text)
|
||||
)
|
||||
)
|
||||
else:
|
||||
print("\nNo replies yet. Be the first!")
|
||||
|
||||
print("")
|
||||
print_page_count(pages)
|
||||
output.append("\n No replies yet. Be the first!")
|
||||
return output
|
||||
|
||||
|
||||
def print_search_results(keyword: str, search_results: list):
|
||||
|
@ -347,53 +336,49 @@ def post_link() -> int:
|
|||
return LinkData.add(record)
|
||||
|
||||
|
||||
class Page:
|
||||
class Output:
|
||||
def __init__(self):
|
||||
self.current: int = 1 # the current page
|
||||
self.count: int = 0 # the total count of pages
|
||||
self.length: int = None # the number of lines on a page
|
||||
self.current_slice: slice = None # a slice to display the current page data
|
||||
self.thead = None
|
||||
self.content = None
|
||||
self.length = None
|
||||
self.miny = None
|
||||
self.maxy = None
|
||||
self.scrollminy = 0
|
||||
self.viewslice = None
|
||||
|
||||
def calculate_pages(self, content_len: int, boilerplate_len: int):
|
||||
_, lines = get_terminal_size()
|
||||
self.length = lines - boilerplate_len
|
||||
self.count = ceil(content_len / self.length)
|
||||
self.get_pages_current_slice()
|
||||
|
||||
def next(self):
|
||||
if self.current < self.count:
|
||||
self.current += 1
|
||||
|
||||
def prev(self):
|
||||
if self.current > 1:
|
||||
self.current -= 1
|
||||
|
||||
def get_pages_current_slice(self):
|
||||
self.current_slice = slice(
|
||||
(self.length * (self.current - 1)), (self.length * self.current)
|
||||
)
|
||||
def Calc(self):
|
||||
if not self.content:
|
||||
raise ValueError
|
||||
# TODO: ???
|
||||
self.length = len(self.content)
|
||||
if self.thead:
|
||||
self.miny = 3
|
||||
else:
|
||||
self.miny = 2
|
||||
self.maxy = curses.LINES - 3
|
||||
self.viewslice = slice(self.scrollminy, self.scrollminy + self.maxy)
|
||||
|
||||
|
||||
class Level:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.data = None
|
||||
self.pages = Page()
|
||||
self.selected_index = None
|
||||
self.output = Output()
|
||||
self.selected_key = None
|
||||
|
||||
|
||||
class Menu:
|
||||
def __init__(self):
|
||||
# main levels: categories -> category_details -> thread_details
|
||||
# main levels: categories -> links -> post
|
||||
categories = Level("categories")
|
||||
category_details = Level("category_details")
|
||||
thread_details = Level("thread_details")
|
||||
self.main_levels = [categories, category_details, thread_details]
|
||||
links = Level("links")
|
||||
post = Level("post")
|
||||
self.main_levels = [categories, links, post]
|
||||
|
||||
# search levels: search_results -> search_results_thread_details
|
||||
# search levels: search_results -> found_post
|
||||
search_results = Level("search_results")
|
||||
search_results_thread_details = Level("search_results_thread_details")
|
||||
self.search_levels = [search_results, search_results_thread_details]
|
||||
found_post = Level("found_post")
|
||||
self.search_levels = [search_results, found_post]
|
||||
|
||||
self.is_main_level = True
|
||||
|
||||
|
@ -402,9 +387,6 @@ class Menu:
|
|||
self.current_level = self.main_levels[self.main_level_index]
|
||||
|
||||
def back(self):
|
||||
# TODO: back should probably go to previous pages before going up a
|
||||
# level? main controls could be next page and "back" which would be a
|
||||
# combo of previous page and go back a level
|
||||
if self.is_main_level:
|
||||
if self.main_level_index > 0:
|
||||
self.main_level_index -= 1
|
||||
|
@ -428,36 +410,102 @@ class Menu:
|
|||
else:
|
||||
self.current_level = self.main_levels[self.main_level_index]
|
||||
|
||||
def get_data(self, LinkData):
|
||||
"""fill out current level data based on level settings"""
|
||||
if self.current_level.name == "categories":
|
||||
self.current_level.data = LinkData.categories
|
||||
(
|
||||
self.current_level.output.thead,
|
||||
self.current_level.output.content,
|
||||
) = print_categories(self.current_level.data, curses.COLS)
|
||||
|
||||
elif self.current_level.name == "links":
|
||||
self.current_level.data = LinkData.get_links_by_category_name(
|
||||
self.current_level.selected_key
|
||||
)
|
||||
(
|
||||
self.current_level.output.thead,
|
||||
self.current_level.output.content,
|
||||
) = print_links(self.current_level.data, curses.COLS)
|
||||
|
||||
elif self.current_level.name == "post":
|
||||
self.current_level.data = LinkData.get_post(self.current_level.selected_key)
|
||||
self.current_level.output.content = print_post(
|
||||
self.current_level.data, curses.COLS
|
||||
)
|
||||
|
||||
elif self.current_level.name == "search_results":
|
||||
pass
|
||||
elif self.current_level.name == "search_results_thread_details":
|
||||
pass
|
||||
else:
|
||||
raise ValueError
|
||||
# TODO: if this fails it's uninitialised or something
|
||||
|
||||
|
||||
def term_resize(stdscr):
|
||||
"""resizes the provided stdscr to the current size"""
|
||||
# TODO: ensure resize can decrement bodyscrollmin by the appropriate amount
|
||||
# if there is free space available
|
||||
stdscr.clear()
|
||||
curses.resizeterm(*stdscr.getmaxyx())
|
||||
curses.flushinp()
|
||||
curses.update_lines_cols()
|
||||
|
||||
|
||||
def new_main_menu(stdscr):
|
||||
# curses.use_default_colors()
|
||||
# curses.curs_set(0)
|
||||
|
||||
title = "Linkulator".encode(code)
|
||||
body = print_categories(LinkData.categories, curses.COLS)
|
||||
|
||||
menu = Menu()
|
||||
menu.get_data(LinkData)
|
||||
menu.current_level.output.Calc()
|
||||
|
||||
status = "".encode(code)
|
||||
action = "waiting".encode(code)
|
||||
|
||||
bodyminy = 2
|
||||
bodyscrollmin = 0
|
||||
|
||||
while True:
|
||||
# stdscr.erase()
|
||||
# print title
|
||||
stdscr.addstr(0, 1, title)
|
||||
stdscr.clrtoeol()
|
||||
# TODO: subtitle?
|
||||
# stdscr.addstr(1, 4, "All categories".encode(code))
|
||||
|
||||
# print header
|
||||
if menu.current_level.output.thead:
|
||||
stdscr.addstr(
|
||||
menu.current_level.output.miny - 1,
|
||||
0,
|
||||
menu.current_level.output.thead.encode(code),
|
||||
)
|
||||
stdscr.clrtoeol()
|
||||
|
||||
# print body
|
||||
bodymaxy = curses.LINES - 2
|
||||
viewslice = slice(bodyscrollmin, bodyscrollmin + bodymaxy)
|
||||
menu.current_level.output.Calc()
|
||||
|
||||
count = 0
|
||||
for i, lines in enumerate(body[viewslice]):
|
||||
for i, line in enumerate(
|
||||
menu.current_level.output.content[menu.current_level.output.viewslice]
|
||||
):
|
||||
count += count
|
||||
stdscr.addstr(i + bodyminy, 0, lines.encode(code))
|
||||
stdscr.clrtoeol()
|
||||
for i in range((bodymaxy) - count):
|
||||
stdscr.addstr(i + menu.current_level.output.miny, 0, line.encode(code))
|
||||
stdscr.clrtoeol()
|
||||
|
||||
# this is meant to clear to the second last line, but...
|
||||
# just erase to the bottom for now
|
||||
stdscr.clrtobot()
|
||||
# leftover = menu.current_level.output.maxy - count
|
||||
# if leftover > 0:
|
||||
# for i in range(
|
||||
# (menu.current_level.output.maxy - menu.current_level.output.miny)
|
||||
# - count
|
||||
# ):
|
||||
# stdscr.move(count + i + 1, 0)
|
||||
# stdscr.clrtoeol()
|
||||
|
||||
stdscr.refresh()
|
||||
|
||||
# print status
|
||||
|
@ -474,23 +522,35 @@ def new_main_menu(stdscr):
|
|||
if k == "q":
|
||||
graceful_exit()
|
||||
elif k == "KEY_RESIZE":
|
||||
stdscr.clear()
|
||||
curses.resizeterm(*stdscr.getmaxyx())
|
||||
curses.flushinp()
|
||||
curses.update_lines_cols()
|
||||
term_resize(stdscr)
|
||||
action = "resize"
|
||||
elif k == "j":
|
||||
if (bodyscrollmin <= (len(body) - bodymaxy)):
|
||||
bodyscrollmin += 1
|
||||
# TODO: put controls in output class
|
||||
if menu.current_level.output.scrollminy <= (
|
||||
menu.current_level.output.length - menu.current_level.output.maxy
|
||||
):
|
||||
menu.current_level.output.scrollminy += 1
|
||||
action = "down"
|
||||
else:
|
||||
action = "tried down"
|
||||
elif k == "k":
|
||||
if bodyscrollmin > 0:
|
||||
bodyscrollmin -= 1
|
||||
if menu.current_level.output.scrollminy > 0:
|
||||
menu.current_level.output.scrollminy -= 1
|
||||
action = "up"
|
||||
else:
|
||||
action = "tried up"
|
||||
elif k == "b":
|
||||
menu.back()
|
||||
elif k in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]:
|
||||
action = int(k)
|
||||
if menu.current_level.name == "categories":
|
||||
key = menu.current_level.data[action - 1]["name"]
|
||||
elif menu.current_level.name == "links":
|
||||
key = menu.current_level.data[action - 1]["post_id"]
|
||||
menu.forward()
|
||||
menu.current_level.selected_key = key
|
||||
menu.get_data(LinkData)
|
||||
menu.current_level.output.Calc()
|
||||
|
||||
status = "{}".format(action)
|
||||
status.encode(code)
|
||||
|
@ -498,59 +558,6 @@ def new_main_menu(stdscr):
|
|||
stdscr.refresh()
|
||||
|
||||
|
||||
def main_menu():
|
||||
"""Displays list of categories, takes keyboard input and
|
||||
executes corresponding functions."""
|
||||
|
||||
boilerplate_len = 10
|
||||
|
||||
menu = Menu()
|
||||
|
||||
# each menu level consists of:
|
||||
# data (a list or dictionary) that gets printed
|
||||
# this data may be based on an index chosen by the previous level
|
||||
# page settings which are used to calculate the index
|
||||
# input which is handled
|
||||
|
||||
while True:
|
||||
# print current menu level
|
||||
if menu.current_level.name == "categories":
|
||||
menu.current_level.data = LinkData.categories
|
||||
menu.current_level.pages.calculate_pages(
|
||||
len(menu.current_level.data), boilerplate_len
|
||||
)
|
||||
print_categories(menu.current_level.data, menu.current_level.pages)
|
||||
change_level(menu)
|
||||
|
||||
elif menu.current_level.name == "category_details":
|
||||
menu.current_level.data = LinkData.list_category_details(
|
||||
menu.main_levels[0].data[menu.current_level.selected_index]["name"]
|
||||
)
|
||||
menu.current_level.pages.calculate_pages(
|
||||
len(menu.current_level.data), boilerplate_len
|
||||
)
|
||||
print_category_details(
|
||||
menu.current_level.data[menu.current_level.pages.current_slice],
|
||||
menu.current_level.pages,
|
||||
)
|
||||
change_level(menu)
|
||||
|
||||
elif menu.current_level.name == "thread_details":
|
||||
menu.current_level.data = LinkData.get_thread_details(
|
||||
menu.main_levels[1].data[menu.current_level.selected_index]["postid"]
|
||||
)
|
||||
menu.current_level.pages.calculate_pages(
|
||||
len(menu.current_level.data), boilerplate_len
|
||||
)
|
||||
print_thread_details(menu.current_level.data, menu.current_level.pages)
|
||||
change_level(menu)
|
||||
|
||||
elif menu.current_level.name == "search_results":
|
||||
pass
|
||||
elif menu.current_level.name == "search_results_thread_details":
|
||||
pass
|
||||
|
||||
|
||||
def change_level(menu) -> int:
|
||||
"""???"""
|
||||
while True:
|
||||
|
|
|
@ -6,41 +6,42 @@ from unittest.mock import patch, call
|
|||
import linkulator
|
||||
|
||||
|
||||
class TestPrintSearchResults(unittest.TestCase):
|
||||
"""Tests covering the print_search_results function"""
|
||||
# class TestPrintSearchResults(unittest.TestCase):
|
||||
# """Tests covering the print_search_results function"""
|
||||
|
||||
@patch("builtins.print")
|
||||
def test_print_search_results(self, mock_print):
|
||||
"""tests that the search results are printed correctly"""
|
||||
test_keyword = "keyword"
|
||||
test_search_results = [
|
||||
(66, "keyword", "1576461366.5580268", "", "c", "c", "c"),
|
||||
(65, "poster6", "1576461367.5580268", "", "keyword", "c", "c"),
|
||||
(64, "poster7", "1576461368.5580268", "", "c", "keyword", "c"),
|
||||
(63, "poster8", "1576461369.5580268", "", "c", "c", "keyword"),
|
||||
]
|
||||
test_print_calls = [
|
||||
call(
|
||||
"\nShowing results for keyword\n\n ID# DATE AUTHOR DESC "
|
||||
),
|
||||
call(" 1 2019-12-16 keyword c "),
|
||||
call(" 2 2019-12-16 poster6 c "),
|
||||
call(" 3 2019-12-16 poster7 c "),
|
||||
call(" 4 2019-12-16 poster8 keyword "),
|
||||
call(""),
|
||||
]
|
||||
|
||||
linkulator.print_search_results(test_keyword, test_search_results)
|
||||
|
||||
self.assertEqual(
|
||||
mock_print.call_count, 6
|
||||
) # one count for title, 4 for the items and a blank line for formatting
|
||||
|
||||
self.assertListEqual(test_print_calls, mock_print.call_args_list)
|
||||
# @patch("builtins.print")
|
||||
# def test_print_search_results(self, mock_print):
|
||||
# """tests that the search results are printed correctly"""
|
||||
# test_keyword = "keyword"
|
||||
# test_search_results = [
|
||||
# (66, "keyword", "1576461366.5580268", "", "c", "c", "c"),
|
||||
# (65, "poster6", "1576461367.5580268", "", "keyword", "c", "c"),
|
||||
# (64, "poster7", "1576461368.5580268", "", "c", "keyword", "c"),
|
||||
# (63, "poster8", "1576461369.5580268", "", "c", "c", "keyword"),
|
||||
# ]
|
||||
# test_print_calls = [
|
||||
# call(
|
||||
# "\nShowing results for keyword\n\n ID# DATE AUTHOR DESC "
|
||||
# ),
|
||||
# call(" 1 2019-12-16 keyword c "),
|
||||
# call(" 2 2019-12-16 poster6 c "),
|
||||
# call(" 3 2019-12-16 poster7 c "),
|
||||
# call(" 4 2019-12-16 poster8 keyword "),
|
||||
# call(""),
|
||||
# ]
|
||||
#
|
||||
# linkulator.print_search_results(test_keyword, test_search_results)
|
||||
#
|
||||
# self.assertEqual(
|
||||
# mock_print.call_count, 6
|
||||
# ) # one count for title, 4 for the items and a blank line for formatting
|
||||
#
|
||||
# self.assertListEqual(test_print_calls, mock_print.call_args_list)
|
||||
|
||||
|
||||
class TestPrintCategories(unittest.TestCase):
|
||||
def test_general(self):
|
||||
def test_print_categories(self):
|
||||
"""Test general output of print_categories"""
|
||||
|
||||
categories = [
|
||||
{
|
||||
|
@ -61,22 +62,138 @@ class TestPrintCategories(unittest.TestCase):
|
|||
]
|
||||
cols = 80
|
||||
|
||||
test_results = [
|
||||
test_results = (
|
||||
" ID# New Category",
|
||||
" 1 * category 1 (1)",
|
||||
" 2 * category 2 (2)",
|
||||
" 3 * long category name that will be truncated because it's a long... (20)",
|
||||
]
|
||||
[
|
||||
" 1 * category 1 (1)",
|
||||
" 2 * category 2 (2)",
|
||||
" 3 * long category name that will be truncated because it's a long... (20)",
|
||||
],
|
||||
)
|
||||
|
||||
test_output = linkulator.print_categories(categories, cols)
|
||||
self.assertListEqual(test_output, test_results)
|
||||
self.assertTupleEqual(test_output, test_results)
|
||||
|
||||
def test_empty_categories(self):
|
||||
"""Test output when no categories data"""
|
||||
empty_categories = []
|
||||
cols = 80
|
||||
test_results = [
|
||||
test_results = (
|
||||
"",
|
||||
" There are no posts yet - enter p to post a new link",
|
||||
]
|
||||
["", "There are no posts yet - enter p to post a new link"],
|
||||
)
|
||||
test_output = linkulator.print_categories(empty_categories, cols)
|
||||
self.assertTupleEqual(test_output, test_results)
|
||||
|
||||
|
||||
class TestPrintLinks(unittest.TestCase):
|
||||
def test_print_links(self):
|
||||
"""Test general output of print_links"""
|
||||
|
||||
links = [
|
||||
{
|
||||
"post_id": 1,
|
||||
"link_timestamp": "1627549445.044661",
|
||||
"link_author": "auth 1",
|
||||
"reply_count": 0,
|
||||
"description": "description 1",
|
||||
"has_new_replies": False,
|
||||
"last_modified_timestamp": "1627549445.044661",
|
||||
},
|
||||
{
|
||||
"post_id": 2,
|
||||
"link_timestamp": "1627549445.044661",
|
||||
"link_author": "author 2",
|
||||
"reply_count": 25,
|
||||
"description": "a long description for the second post that should wrap i guess",
|
||||
"has_new_replies": True,
|
||||
"last_modified_timestamp": "1627549445.044661",
|
||||
},
|
||||
]
|
||||
|
||||
cols = 80
|
||||
test_results = (
|
||||
" ID# Date Author #Repl Description",
|
||||
[
|
||||
" 1 2021-07-29 auth 1 [ 0] description 1",
|
||||
" 2 2021-07-29 author 2 [ 25] a long description for the second post...*",
|
||||
],
|
||||
)
|
||||
|
||||
test_output = linkulator.print_links(links, cols)
|
||||
self.assertTupleEqual(test_output, test_results)
|
||||
|
||||
|
||||
class TestPrintPost(unittest.TestCase):
|
||||
def test_post_without_reply(self):
|
||||
"""Test print_post where the post has no reply"""
|
||||
|
||||
post = {
|
||||
"author": "post author 1",
|
||||
"category": "test category 1",
|
||||
"timestamp": "100",
|
||||
"parent_id": "author+timestamp",
|
||||
"replies": [],
|
||||
"title": "A cool website",
|
||||
"url": "http://asdflkjasdf",
|
||||
}
|
||||
|
||||
cols = 80
|
||||
test_results = [
|
||||
" Title : A cool website",
|
||||
" Link : http://asdflkjasdf",
|
||||
" Category : test category 1",
|
||||
" User : post author 1",
|
||||
" Date : Thu 01 Jan 1970 10:01:40",
|
||||
"\n No replies yet. Be the first!",
|
||||
]
|
||||
|
||||
test_output = linkulator.print_post(post, cols)
|
||||
self.assertListEqual(test_output, test_results)
|
||||
|
||||
def test_post_with_reply(self):
|
||||
"""Test print_post where the post has a reply"""
|
||||
|
||||
post = {
|
||||
"author": "author2",
|
||||
"category": "category 2",
|
||||
"timestamp": "1000",
|
||||
"parent_id": "xxxxxxxxx",
|
||||
"replies": [
|
||||
[
|
||||
"",
|
||||
"reply author 1",
|
||||
"1001",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"a reply",
|
||||
],
|
||||
[
|
||||
"",
|
||||
"reply author 2 with a long long long name, a very long name",
|
||||
"1002",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"a reply with a lot of words in it, too many to read, not going to read this",
|
||||
],
|
||||
],
|
||||
"title": "Website 2",
|
||||
"url": "asdflkjasdf",
|
||||
}
|
||||
|
||||
cols = 80
|
||||
test_results = [
|
||||
" Title : Website 2",
|
||||
" Link : asdflkjasdf",
|
||||
" Category : category 2",
|
||||
" User : author2",
|
||||
" Date : Thu 01 Jan 1970 10:16:40",
|
||||
"\n Replies:\n",
|
||||
" 1970-01-01 10:16 reply author 1: a reply",
|
||||
" 1970-01-01 10:16 reply author 2 with a long long long name, a very long name: a reply with a lot of words in it, too many to read, not going to read this",
|
||||
]
|
||||
|
||||
test_output = linkulator.print_post(post, cols)
|
||||
self.assertListEqual(test_output, test_results)
|
||||
|
|
Loading…
Reference in New Issue
Block a user