Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ local.properties
.settings/
.loadpath
.recommenders
.DS_Store
Doc/

# External tool builders
.externalToolBuilders/
Expand Down
14 changes: 5 additions & 9 deletions src/main/java/edu/mills/cs180a/wordui/FXMLController.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public void changed(ObservableValue<? extends WordRecord> observable, WordRecord
modifiedProperty.set(false);
if (newValue != null) {
wordTextField.setText(selectedWordRecord.getWord());

frequencyTextField.setText(Integer.toString(selectedWordRecord.getFrequency()));
freqValidProperty.set(isValidFrequency(frequencyTextField.textProperty()));
definitionTextArea.setText(selectedWordRecord.getDefinition());
Expand Down Expand Up @@ -114,8 +115,7 @@ private void setupListView() {
}

private void populateChoiceBox() {
sortChoiceBox.setItems(FXCollections.observableArrayList(
WordRecord.SortOrder.values()));
sortChoiceBox.setItems(FXCollections.observableArrayList(WordRecord.SortOrder.values()));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes to this file are all due to formatting. You should keep this type of change out of PRs. The best way to do so it to look at your PR on GitHub before submitting it.

sortChoiceBox.setValue(WordRecord.SortOrder.ALPHABETICALLY_FORWARD);
}

Expand All @@ -128,8 +128,7 @@ private void configureButtons() {
// been made, or any field is empty or invalid.
updateButton.disableProperty()
.bind(listView.getSelectionModel().selectedItemProperty().isNull()
.or(modifiedProperty.not())
.or(freqValidProperty.not())
.or(modifiedProperty.not()).or(freqValidProperty.not())
.or(wordTextField.textProperty().isEmpty())
.or(definitionTextArea.textProperty().isEmpty()));

Expand Down Expand Up @@ -162,11 +161,8 @@ private void handleKeyAction(KeyEvent keyEvent) {
@FXML
private void createButtonAction(ActionEvent actionEvent) {
System.out.println("Create");
WordRecord wordRecord =
new WordRecord(
wordTextField.getText(),
Integer.parseInt(frequencyTextField.getText()),
definitionTextArea.getText());
WordRecord wordRecord = new WordRecord(wordTextField.getText(),
Integer.parseInt(frequencyTextField.getText()), definitionTextArea.getText());
wordRecordList.add(wordRecord);
listView.getSelectionModel().select(wordRecord); // select the new item
}
Expand Down
89 changes: 62 additions & 27 deletions src/main/java/edu/mills/cs180a/wordui/model/SampleData.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,32 @@
import edu.mills.cs180a.wordnik.client.model.WordOfTheDay;
import javafx.collections.ObservableList;

/**
* Create sample data to display on the list in JavaFX.
*
* @author Ellen Spertus
* @author Makie Maekawa

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent! I didn't even ask for people to add class-level javadoc. I should have.

*
*/
public class SampleData {
@VisibleForTesting
protected static final String WORD_KEY = "word";
@VisibleForTesting
protected static final String FREQ_COUNT_KEY = "count";
@VisibleForTesting
protected static final String FREQ_YEAR_KEY = "year";
private static final int FREQ_YEAR = 2012;
@VisibleForTesting
protected static final int FREQ_YEAR = 2012;
private static ApiClient client; // set in fillSampleData()

@VisibleForTesting

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reordering the code made it harder to review.

protected static int getFrequencyByYear(WordApi wordApi, String word, int year) {
FrequencySummary fs = wordApi.getWordFrequency(word, "false", year, year);
return getFrequencyFromSummary(fs, year);
}

private static int getFrequencyFromSummary(FrequencySummary fs, int year) {
List<Object> freqObjects = fs.getFrequency();
// freqObjects is a List<Map> [{"year" = "2012", "count" = 179}] for "Java"

if (freqObjects instanceof List) {
List<Object> maps = (List<Object>) freqObjects;
for (Object map : maps) {
Expand All @@ -42,44 +56,65 @@ private static int getFrequencyFromSummary(FrequencySummary fs, int year) {
return 0;
}

// TODO: Move to spring-swagger-wordnik-client
@VisibleForTesting
protected static int getFrequencyByYear(WordApi wordApi, String word, int year) {
FrequencySummary fs = wordApi.getWordFrequency(word, "false", year, year);
return getFrequencyFromSummary(fs, year);
public static List<Object> getDefinitions(WordsApi WordsApi) {
return WordsApi.getWordOfTheDay().getDefinitions();
}

private static WordRecord buildWordRecord(String word, Map<Object, Object> definition) {
WordApi wordApi = client.buildClient(WordApi.class);
return new WordRecord(
word,
getFrequencyByYear(wordApi, word, FREQ_YEAR),
definition.get("text").toString());
@VisibleForTesting
public static String getWord(WordsApi WordsApi) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should never have a variable name with the same name as a type. I'm surprised that Java allows this. Also, argument names should be lowerCamelCase.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need this method. Instead, call a method in WordOfTheDay.

return WordsApi.getWordOfTheDay().getWord();
}

protected static WordOfTheDay getWordOfTheDay(WordsApi WordsApi) {
return WordsApi.getWordOfTheDay();
}

/**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You weren't asked to create javadoc for this method, which should be @VisibleForTesting, not public. Javadoc should be created only for public and protected methods (or when explicitly requested by me).

* Get definitions and create WordRecord data.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description isn't accurate. A better one would be "Add the word of the day to the list of sample words."

*
* @param wordsApi API key client.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be no period at the end of @param or @tag lines.

* @return Return data to be displayed on the screen. return null, if definitions is null or
* empty.
*/
public static WordRecord addWordOfTheDay(WordsApi wordsApi) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method should be void. As implied by the name, it does something rather than returning something. It should take as an argument the backing list. I will make this clearer in future versions of the assignment.

List<Object> definitions = getWordOfTheDay(wordsApi).getDefinitions();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should not call getWordOfTheDay(wordsApi) more than once. You should save the result of the call into a local variable.

if (definitions != null && !definitions.isEmpty()) {
Object definition = definitions.get(0);
if (definition instanceof Map) {
@SuppressWarnings("unchecked")
Map<Object, Object> definitionAsMap = (Map<Object, Object>) definition;
return (buildWordRecord(getWordOfTheDay(wordsApi).getWord(), definitionAsMap));
}
}
return null;
}

/**
* Create sample data for display on the list.
*
* @param backingList A list that allows listeners to track when something changes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not capitalize parameter descriptions.

*/
public static void fillSampleData(ObservableList<WordRecord> backingList) {
try {
client = ApiClientHelper.getApiClient();
WordsApi wordsApi = client.buildClient(WordsApi.class);
WordOfTheDay word = wordsApi.getWordOfTheDay();
List<Object> definitions = word.getDefinitions();
if (definitions != null && !definitions.isEmpty()) {
Object definition = definitions.get(0);
if (definition instanceof Map) {
@SuppressWarnings("unchecked")
Map<Object, Object> definitionAsMap = (Map<Object, Object>) definition;
backingList.add(buildWordRecord(word.getWord(), definitionAsMap));
}
}
backingList.add(addWordOfTheDay(wordsApi));
} catch (IOException e) {
System.err.println("Unable to get API key.");
}

backingList.add(new WordRecord("buffalo", 5153, "The North American bison."));
backingList.add(new WordRecord("school", 23736, "A large group of aquatic animals."));
backingList.add(new WordRecord("Java",
179, "An island of Indonesia in the Malay Archipelago"));
backingList.add(new WordRecord("random",
794, "Having no specific pattern, purpose, or objective"));
backingList.add(
new WordRecord("Java", 179, "An island of Indonesia in the Malay Archipelago"));
backingList.add(
new WordRecord("random", 794, "Having no specific pattern, purpose, or objective"));
}

protected static WordRecord buildWordRecord(String word, Map<Object, Object> definition) {
WordApi wordApi = client.buildClient(WordApi.class);
return new WordRecord(word, getFrequencyByYear(wordApi, word, FREQ_YEAR),
definition.get("text").toString());
}
}
91 changes: 83 additions & 8 deletions src/test/java/edu/mills/cs180a/wordui/model/SampleDataTest.java
Original file line number Diff line number Diff line change
@@ -1,33 +1,63 @@
package edu.mills.cs180a.wordui.model;

import static edu.mills.cs180a.wordui.model.SampleData.FREQ_COUNT_KEY;
import static edu.mills.cs180a.wordui.model.SampleData.FREQ_YEAR;
import static edu.mills.cs180a.wordui.model.SampleData.FREQ_YEAR_KEY;
import static edu.mills.cs180a.wordui.model.SampleData.getFrequencyByYear;
import static edu.mills.cs180a.wordui.model.SampleData.getWordOfTheDay;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import edu.mills.cs180a.wordnik.client.api.WordApi;
import edu.mills.cs180a.wordnik.client.api.WordsApi;
import edu.mills.cs180a.wordnik.client.model.FrequencySummary;
import edu.mills.cs180a.wordnik.client.model.WordOfTheDay;

class SampleDataTest {
private final WordApi mockWordApi = mock(WordApi.class);
private static final Map<String, FrequencySummary> FREQS_MAP = Map.of(
"apple", makeFrequencySummary(List.of(makeMap(2000, 339), makeMap(2001, 464))),
"orange", makeFrequencySummary(List.of(makeMap(2000, 774), makeMap(2001, 941))));
private final WordsApi mockWordsApi = mock(WordsApi.class);
private static final Map<String, FrequencySummary> FREQS_MAP = Map.of("apple",
makeFrequencySummary(List.of(makeMap(2000, 339), makeMap(2001, 464))), "orange",
makeFrequencySummary(List.of(makeMap(2000, 774), makeMap(2001, 941))), "blueberry",
makeFrequencySummary(List.of(makeMap(2000, 10), makeMap(2001, 58))), "airplane",
makeFrequencySummary(List.of(makeMap(2012, 353))));

private static final WordOfTheDay MOCK_WORD = makeWordOfTheDay("airplane",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good, but I would call it MOCK_WORD_OF_THE_DAY or MOCK_WOD.

List.of("Any of various winged vehicles capable of flight, "
+ "generally heavier than air and driven by jet engines or propellers."));

private static final List<Object> DIFINITION_MAP = new ArrayList<Object>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DEFINITION_MAP (typo)

Arrays.asList(makeMapDefin("Any of various winged vehicles capable of flight,"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't repeat a string. You should put it in a constant.

+ "generally heavier than air and driven by jet engines or propellers.")));

private static final WordRecord MOCK_WORDRECORD =
new WordRecord("airplane", 353, "Any of various winged vehicles capable of flight,"
+ "generally heavier than air and driven by jet engines or propellers.");

@BeforeEach
void setup() {
when(mockWordApi.getWordFrequency(anyString(), anyString(), anyInt(), anyInt()))
.thenAnswer(invocation -> FREQS_MAP.get(invocation.getArgument(0)));
when(mockWordsApi.getWordOfTheDay()).thenReturn(MOCK_WORD);
when(mockWordsApi.getWordOfTheDay().getWord()).thenReturn("blueberry");
when(mockWordsApi.getWordOfTheDay().getDefinitions())
.thenReturn(List.of("Any of various plants of the genus"));
}

private static Map<Object, Object> makeMap(int year, int count) {
return Map.of(SampleData.FREQ_YEAR_KEY, String.valueOf(year),
SampleData.FREQ_COUNT_KEY, count);
return Map.of(FREQ_YEAR_KEY, String.valueOf(year), FREQ_COUNT_KEY, count);
}

private static FrequencySummary makeFrequencySummary(List<Object> freqs) {
Expand All @@ -36,10 +66,55 @@ private static FrequencySummary makeFrequencySummary(List<Object> freqs) {
return fs;
}

private static Map<Object, Object> makeMapDefin(String defin) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better name would be makeMapDefinition.

Map<Object, Object> map = new LinkedHashMap<>(Map.of("text", String.valueOf(defin)));
return map;
}

@ParameterizedTest
@CsvSource({"apple,2000,339", "apple,2001,464", "apple,2020,0", "orange,2000,774",
"orange,2001,941", "orange,2050,0"})
void testGetFrequencyFromSummary(String word, int year, int count) {
assertEquals(count, SampleData.getFrequencyByYear(mockWordApi, word, year));
"orange,2001,941", "orange,2050,0", "airplane,2012,353"})
void getFrequencyByYear_Equal_correctValue(String word, int year, int count) {
assertEquals(count, getFrequencyByYear(mockWordApi, word, year));
}

private static WordOfTheDay makeWordOfTheDay(String word, List<Object> defin) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good helper method.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be clear to make the second argument a String and for this method to call makeMapDefin. That makes things easier for this method's caller and improves abstraction.

WordOfTheDay wd = mock(WordOfTheDay.class);
when(wd.getWord()).thenReturn(word);
when(wd.getDefinitions()).thenReturn(defin);
return wd;
}

@Test
void getWord_Equal_correctValue() {
assertEquals("blueberry", getWordOfTheDay(mockWordsApi).getWord());
}

@Test
void getDefinitions_Equal_correctValue() {
assertEquals("Any of various plants of the genus",
getWordOfTheDay(mockWordsApi).getDefinitions().get(0));
}

@SuppressWarnings("static-access")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than suppressing warnings, you should deal with the underlying issue, which I'm happy to discuss.

@Test
void addWordOfTheDay_Equal_correctValue() {
SampleData sd = mock(SampleData.class);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to mock the class.

when(sd.getWordOfTheDay(mockWordsApi).getDefinitions()).thenReturn(DIFINITION_MAP);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should not mock within the test.

when(sd.getWordOfTheDay(mockWordsApi).getWord()).thenReturn("airplane");

List<Object> getDefin = sd.getWordOfTheDay(mockWordsApi).getDefinitions();
Object definition = getDefin.get(0);
@SuppressWarnings("unchecked")
Map<Object, Object> definitionAsMap = (Map<Object, Object>) definition;

WordRecord testWordRecord = new WordRecord(
sd.getWordOfTheDay(mockWordsApi).getWord(), getFrequencyByYear(mockWordApi,
sd.getWordOfTheDay(mockWordsApi).getWord(), FREQ_YEAR),
definitionAsMap.get("text").toString());

assertTrue(MOCK_WORDRECORD.getWord().equals(testWordRecord.getWord())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should have a series of assertEquals() statements. Those would give better error messages than a single assertTrue().

&& MOCK_WORDRECORD.getFrequency().equals(testWordRecord.getFrequency())
&& MOCK_WORDRECORD.getDefinition().equals(testWordRecord.getDefinition()));
}
}
1 change: 1 addition & 0 deletions wordui
Submodule wordui added at 249c33