/** Test giving the answer for a reviewed card */ public void testAnswerCard() { Collection col; col = CollectionHelper.getInstance().getCol(getContext()); Sched sched = col.getSched(); long deckId = mTestDeckIds[0]; col.getDecks().select(deckId); Card card = sched.getCard(); ContentResolver cr = getContext().getContentResolver(); Uri reviewInfoUri = FlashCardsContract.ReviewInfo.CONTENT_URI; ContentValues values = new ContentValues(); long noteId = card.note().getId(); int cardOrd = card.getOrd(); int ease = AbstractFlashcardViewer.EASE_3; // <- insert real ease here values.put(FlashCardsContract.ReviewInfo.NOTE_ID, noteId); values.put(FlashCardsContract.ReviewInfo.CARD_ORD, cardOrd); values.put(FlashCardsContract.ReviewInfo.EASE, ease); int updateCount = cr.update(reviewInfoUri, values, null, null); assertEquals("Check if update returns 1", 1, updateCount); sched.reset(); Card newCard = sched.getCard(); if (newCard != null) { if (newCard.note().getId() == card.note().getId() && newCard.getOrd() == card.getOrd()) { fail("Next scheduled card has not changed"); } } else { // We expected this } }
/** * Test query to specific deck ID * * @throws Exception */ public void testQueryCertainDeck() throws Exception { Collection col; col = CollectionHelper.getInstance().getCol(getContext()); long deckId = mTestDeckIds[0]; Uri deckUri = Uri.withAppendedPath(FlashCardsContract.Deck.CONTENT_ALL_URI, Long.toString(deckId)); Cursor decksCursor = getContext().getContentResolver().query(deckUri, null, null, null, null); try { if (decksCursor == null || !decksCursor.moveToFirst()) { fail("No deck received. Should have delivered deck with id " + deckId); } else { long returnedDeckID = decksCursor.getLong(decksCursor.getColumnIndex(FlashCardsContract.Deck.DECK_ID)); String returnedDeckName = decksCursor.getString(decksCursor.getColumnIndex(FlashCardsContract.Deck.DECK_NAME)); JSONObject realDeck = col.getDecks().get(deckId); assertEquals("Check that received deck ID equals real deck ID", deckId, returnedDeckID); assertEquals( "Check that received deck name equals real deck name", realDeck.getString("name"), returnedDeckName); } } finally { decksCursor.close(); } }
/** * Test query to decks table * * @throws Exception */ public void testQueryAllDecks() throws Exception { Collection col; col = CollectionHelper.getInstance().getCol(getContext()); Decks decks = col.getDecks(); Cursor decksCursor = getContext() .getContentResolver() .query( FlashCardsContract.Deck.CONTENT_ALL_URI, FlashCardsContract.Deck.DEFAULT_PROJECTION, null, null, null); assertNotNull(decksCursor); try { assertEquals("Check number of results", decks.count(), decksCursor.getCount()); while (decksCursor.moveToNext()) { long deckID = decksCursor.getLong(decksCursor.getColumnIndex(FlashCardsContract.Deck.DECK_ID)); String deckName = decksCursor.getString(decksCursor.getColumnIndex(FlashCardsContract.Deck.DECK_NAME)); JSONObject deck = decks.get(deckID); assertNotNull("Check that the deck we received actually exists", deck); assertEquals( "Check that the received deck has the correct name", deck.getString("name"), deckName); } } finally { decksCursor.close(); } }
/** Check that inserting and removing a note into default deck works as expected */ public void testInsertAndRemoveNote() throws Exception { // Get required objects for test final ContentResolver cr = getContext().getContentResolver(); final Collection col = CollectionHelper.getInstance().getCol(getContext()); final AddContentApi api = new AddContentApi(getContext()); // Add the note Uri newNoteUri = api.addNewNote(mModelId, 1, TEST_NOTE_FIELDS, TEST_TAG); assertNotNull("Check that URI returned from addNewNote is not null", newNoteUri); // Check that it looks as expected Note addedNote = new Note(col, Long.parseLong(newNoteUri.getLastPathSegment())); addedNote.load(); assertTrue( "Check that fields were set correctly", Arrays.equals(addedNote.getFields(), TEST_NOTE_FIELDS)); assertEquals("Check that tag was set correctly", TEST_TAG, addedNote.getTags().get(0)); int expectedNumCards = col.getModels().get(mModelId).getJSONArray("tmpls").length(); assertEquals( "Check that correct number of cards generated", expectedNumCards, addedNote.cards().size()); // Now delete the note cr.delete(newNoteUri, null, null); try { addedNote.load(); fail("Expected RuntimeException to be thrown when deleting note"); } catch (RuntimeException e) { // Expect RuntimeException to be thrown when loading deleted note } }
/** Remove the notes and decks created in setUp(). */ @Override protected void tearDown() throws Exception { Log.i(AnkiDroidApp.TAG, "tearDown()"); final Collection col = CollectionHelper.getInstance().getCol(getContext()); // Delete all notes List<Long> remnantNotes = col.findNotes("tag:" + TEST_TAG); if (remnantNotes.size() > 0) { long[] nids = new long[remnantNotes.size()]; for (int i = 0; i < remnantNotes.size(); i++) { nids[i] = remnantNotes.get(i); } col.remNotes(nids); col.save(); assertEquals( "Check that remnant notes have been deleted", 0, col.findNotes("tag:" + TEST_TAG).size()); } // delete test decks for (long did : mTestDeckIds) { col.getDecks().rem(did, true); } col.getDecks().flush(); assertEquals( "Check that all created decks have been deleted", mNumDecksBeforeTest, col.getDecks().count()); // Delete test model col.modSchema(false); col.getModels().rem(col.getModels().get(mModelId)); super.tearDown(); }
/** Check that inserting a new model works as expected */ public void testInsertAndUpdateModel() throws Exception { final ContentResolver cr = getContext().getContentResolver(); ContentValues cv = new ContentValues(); // Insert a new model cv.put(FlashCardsContract.Model.NAME, TEST_MODEL_NAME); cv.put(FlashCardsContract.Model.FIELD_NAMES, Utils.joinFields(TEST_MODEL_FIELDS)); cv.put(FlashCardsContract.Model.NUM_CARDS, TEST_MODEL_CARDS.length); cv.put(FlashCardsContract.Model.CSS, TEST_MODEL_CSS); Uri modelUri = cr.insert(FlashCardsContract.Model.CONTENT_URI, cv); assertNotNull("Check inserted model isn't null", modelUri); long mid = Long.parseLong(modelUri.getLastPathSegment()); final Collection col = CollectionHelper.getInstance().getCol(getContext()); try { JSONObject model = col.getModels().get(mid); assertEquals("Check model name", TEST_MODEL_NAME, model.getString("name")); assertEquals("Check css", TEST_MODEL_CSS, model.getString("css")); assertEquals( "Check templates length", TEST_MODEL_CARDS.length, model.getJSONArray("tmpls").length()); assertEquals( "Check field length", TEST_MODEL_FIELDS.length, model.getJSONArray("flds").length()); JSONArray flds = model.getJSONArray("flds"); for (int i = 0; i < flds.length(); i++) { assertEquals( "Check name of fields", flds.getJSONObject(i).getString("name"), TEST_MODEL_FIELDS[i]); } // Update each of the templates in the model for (int i = 0; i < TEST_MODEL_CARDS.length; i++) { cv = new ContentValues(); cv.put(FlashCardsContract.CardTemplate.NAME, TEST_MODEL_CARDS[i]); cv.put(FlashCardsContract.CardTemplate.QUESTION_FORMAT, TEST_MODEL_QFMT[i]); cv.put(FlashCardsContract.CardTemplate.ANSWER_FORMAT, TEST_MODEL_AFMT[i]); cv.put(FlashCardsContract.CardTemplate.BROWSER_QUESTION_FORMAT, TEST_MODEL_QFMT[i]); cv.put(FlashCardsContract.CardTemplate.BROWSER_ANSWER_FORMAT, TEST_MODEL_AFMT[i]); Uri tmplUri = Uri.withAppendedPath(Uri.withAppendedPath(modelUri, "templates"), Integer.toString(i)); assertTrue("Update rows", cr.update(tmplUri, cv, null, null) > 0); JSONObject template = col.getModels().get(mid).getJSONArray("tmpls").getJSONObject(i); assertEquals("Check template name", TEST_MODEL_CARDS[i], template.getString("name")); assertEquals("Check qfmt", TEST_MODEL_QFMT[i], template.getString("qfmt")); assertEquals("Check afmt", TEST_MODEL_AFMT[i], template.getString("afmt")); assertEquals("Check bqfmt", TEST_MODEL_QFMT[i], template.getString("bqfmt")); assertEquals("Check bafmt", TEST_MODEL_AFMT[i], template.getString("bafmt")); } } finally { // Delete the model (this will force a full-sync) try { col.modSchema(false); col.getModels().rem(col.getModels().get(mid)); } catch (ConfirmModSchemaException e) { // This will never happen throw new IllegalStateException( "Unexpected ConfirmModSchemaException trying to remove model"); } } }
/** Test changing the selected deck */ public void testSetSelectedDeck() { long deckId = mTestDeckIds[0]; ContentResolver cr = getContext().getContentResolver(); Uri selectDeckUri = FlashCardsContract.Deck.CONTENT_SELECTED_URI; ContentValues values = new ContentValues(); values.put(FlashCardsContract.Deck.DECK_ID, deckId); cr.update(selectDeckUri, values, null, null); Collection col; col = CollectionHelper.getInstance().getCol(getContext()); assertEquals( "Check that the selected deck has been correctly set", deckId, col.getDecks().selected()); }
/** * Test that query for the next card in the schedule returns a valid result WITH a deck selector */ public void testQueryCardFromCertainDeck() { long deckToTest = mTestDeckIds[0]; String deckSelector = "deckID=?"; String deckArguments[] = {Long.toString(deckToTest)}; Collection col; col = CollectionHelper.getInstance().getCol(getContext()); Sched sched = col.getSched(); long selectedDeckBeforeTest = col.getDecks().selected(); col.getDecks().select(1); // select Default deck Cursor reviewInfoCursor = getContext() .getContentResolver() .query( FlashCardsContract.ReviewInfo.CONTENT_URI, null, deckSelector, deckArguments, null); assertNotNull(reviewInfoCursor); assertEquals("Check that we actually received one card", 1, reviewInfoCursor.getCount()); try { reviewInfoCursor.moveToFirst(); int cardOrd = reviewInfoCursor.getInt( reviewInfoCursor.getColumnIndex(FlashCardsContract.ReviewInfo.CARD_ORD)); long noteID = reviewInfoCursor.getLong( reviewInfoCursor.getColumnIndex(FlashCardsContract.ReviewInfo.NOTE_ID)); assertEquals("Check that the selected deck has not changed", 1, col.getDecks().selected()); col.getDecks().select(deckToTest); Card nextCard = null; for (int i = 0; i < 10; i++) { // minimizing fails, when sched.reset() randomly chooses between multiple cards sched.reset(); nextCard = sched.getCard(); if (nextCard.note().getId() == noteID && nextCard.getOrd() == cardOrd) break; } assertNotNull("Check that there actually is a next scheduled card", nextCard); assertEquals( "Check that received card and actual card have same note id", nextCard.note().getId(), noteID); assertEquals( "Check that received card and actual card have same card ord", nextCard.getOrd(), cardOrd); } finally { reviewInfoCursor.close(); } col.getDecks().select(selectedDeckBeforeTest); }
/** Initially create one note for each model. */ @Override protected void setUp() throws Exception { super.setUp(); Log.i(AnkiDroidApp.TAG, "setUp()"); mCreatedNotes = new ArrayList<>(); final Collection col = CollectionHelper.getInstance().getCol(getContext()); // Add a new basic model that we use for testing purposes (existing models could potentially be // corrupted) JSONObject model = Models.addBasicModel(col, BASIC_MODEL_NAME); mModelId = model.getLong("id"); ArrayList<String> flds = col.getModels().fieldNames(model); // Use the names of the fields as test values for the notes which will be added mDummyFields = flds.toArray(new String[flds.size()]); // create test decks and add one note for every deck final AddContentApi api = new AddContentApi(getContext()); HashMap<Long, String> deckList = api.getDeckList(); mNumDecksBeforeTest = deckList.size(); // TODO: add the notes directly with libanki for (int i = 0; i < TEST_DECKS.length; i++) { mTestDeckIds[i] = api.addNewDeck(TEST_DECKS[i]); Uri newNoteUri = api.addNewNote(mModelId, mTestDeckIds[i], mDummyFields, TEST_TAG); assertNotNull(newNoteUri); mCreatedNotes.add(newNoteUri); // Check that the flds data was set correctly long nid = Long.parseLong(newNoteUri.getLastPathSegment()); Note addedNote = col.getNote(nid); assertTrue( "Check that the flds data was set correctly", Arrays.equals(addedNote.getFields(), mDummyFields)); assertTrue("Check that there was at least one card generated", addedNote.cards().size() > 0); } // Add a note to the default deck as well so that testQueryNextCard() works Uri newNoteUri = api.addNewNote(mModelId, 1, mDummyFields, TEST_TAG); assertNotNull(newNoteUri); mCreatedNotes.add(newNoteUri); }
/** * Test that query for the next card in the schedule returns a valid result without any deck * selector */ public void testQueryNextCard() { Collection col; col = CollectionHelper.getInstance().getCol(getContext()); Sched sched = col.getSched(); Cursor reviewInfoCursor = getContext() .getContentResolver() .query(FlashCardsContract.ReviewInfo.CONTENT_URI, null, null, null, null); assertNotNull(reviewInfoCursor); assertEquals("Check that we actually received one card", 1, reviewInfoCursor.getCount()); reviewInfoCursor.moveToFirst(); int cardOrd = reviewInfoCursor.getInt( reviewInfoCursor.getColumnIndex(FlashCardsContract.ReviewInfo.CARD_ORD)); long noteID = reviewInfoCursor.getLong( reviewInfoCursor.getColumnIndex(FlashCardsContract.ReviewInfo.NOTE_ID)); Card nextCard = null; for (int i = 0; i < 10; i++) { // minimizing fails, when sched.reset() randomly chooses between multiple cards sched.reset(); nextCard = sched.getCard(); if (nextCard.note().getId() == noteID && nextCard.getOrd() == cardOrd) break; } assertNotNull("Check that there actually is a next scheduled card", nextCard); assertEquals( "Check that received card and actual card have same note id", nextCard.note().getId(), noteID); assertEquals( "Check that received card and actual card have same card ord", nextCard.getOrd(), cardOrd); }
@Override public MaterialDialog onCreateDialog(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mType = getArguments().getInt("dialogType"); Resources res = getResources(); MaterialDialog.Builder builder = new MaterialDialog.Builder(getActivity()); builder.cancelable(true).title(getTitle()); boolean sqliteInstalled = false; try { sqliteInstalled = Runtime.getRuntime().exec("sqlite3 --version").waitFor() == 0; } catch (IOException | InterruptedException e) { e.printStackTrace(); } switch (mType) { case DIALOG_CURSOR_SIZE_LIMIT_EXCEEDED: case DIALOG_LOAD_FAILED: // Collection failed to load; give user the option of either choosing from repair options, // or closing // the activity return builder .cancelable(false) .content(getMessage()) .iconAttr(R.attr.dialogErrorIcon) .positiveText(res.getString(R.string.error_handling_options)) .negativeText(res.getString(R.string.close)) .callback( new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog dialog) { ((DeckPicker) getActivity()).showDatabaseErrorDialog(DIALOG_ERROR_HANDLING); } @Override public void onNegative(MaterialDialog dialog) { ((DeckPicker) getActivity()).exit(); } }) .show(); case DIALOG_DB_ERROR: // Database Check failed to execute successfully; give user the option of either choosing // from repair // options, submitting an error report, or closing the activity MaterialDialog dialog = builder .cancelable(false) .content(getMessage()) .iconAttr(R.attr.dialogErrorIcon) .positiveText(res.getString(R.string.error_handling_options)) .negativeText(res.getString(R.string.answering_error_report)) .neutralText(res.getString(R.string.close)) .callback( new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog dialog) { ((DeckPicker) getActivity()).showDatabaseErrorDialog(DIALOG_ERROR_HANDLING); } @Override public void onNegative(MaterialDialog dialog) { ((DeckPicker) getActivity()).sendErrorReport(); dismissAllDialogFragments(); } @Override public void onNeutral(MaterialDialog dialog) { ((DeckPicker) getActivity()).exit(); } }) .show(); dialog .getCustomView() .findViewById(R.id.buttonDefaultNegative) .setEnabled(((DeckPicker) getActivity()).hasErrorFiles()); return dialog; case DIALOG_ERROR_HANDLING: // The user has asked to see repair options; allow them to choose one of the repair options // or go back // to the previous dialog ArrayList<String> options = new ArrayList<>(); ArrayList<Integer> values = new ArrayList<>(); if (!((AnkiActivity) getActivity()).colIsOpen()) { // retry options.add(res.getString(R.string.backup_retry_opening)); values.add(0); } else { // fix integrity options.add(res.getString(R.string.check_db)); values.add(1); } // repair db with sqlite if (sqliteInstalled) { options.add(res.getString(R.string.backup_error_menu_repair)); values.add(2); } // // restore from backup options.add(res.getString(R.string.backup_restore)); values.add(3); // delete old collection and build new one options.add(res.getString(R.string.backup_full_sync_from_server)); values.add(4); // delete old collection and build new one options.add(res.getString(R.string.backup_del_collection)); values.add(5); String[] titles = new String[options.size()]; mRepairValues = new int[options.size()]; for (int i = 0; i < options.size(); i++) { titles[i] = options.get(i); mRepairValues[i] = values.get(i); } dialog = builder .iconAttr(R.attr.dialogErrorIcon) .negativeText(res.getString(R.string.dialog_cancel)) .items(titles) .itemsCallback( new MaterialDialog.ListCallback() { @Override public void onSelection( MaterialDialog materialDialog, View view, int which, CharSequence charSequence) { switch (mRepairValues[which]) { case 0: ((DeckPicker) getActivity()).restartActivity(); return; case 1: ((DeckPicker) getActivity()) .showDatabaseErrorDialog(DIALOG_CONFIRM_DATABASE_CHECK); return; case 2: ((DeckPicker) getActivity()) .showDatabaseErrorDialog(DIALOG_REPAIR_COLLECTION); return; case 3: ((DeckPicker) getActivity()) .showDatabaseErrorDialog(DIALOG_RESTORE_BACKUP); return; case 4: ((DeckPicker) getActivity()) .showDatabaseErrorDialog(DIALOG_FULL_SYNC_FROM_SERVER); return; case 5: ((DeckPicker) getActivity()) .showDatabaseErrorDialog(DIALOG_NEW_COLLECTION); } } }) .show(); return dialog; case DIALOG_REPAIR_COLLECTION: // Allow user to run BackupManager.repairCollection() return builder .content(getMessage()) .iconAttr(R.attr.dialogErrorIcon) .positiveText(res.getString(R.string.dialog_positive_repair)) .negativeText(res.getString(R.string.dialog_cancel)) .callback( new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog dialog) { ((DeckPicker) getActivity()).repairDeck(); dismissAllDialogFragments(); } }) .show(); case DIALOG_RESTORE_BACKUP: // Allow user to restore one of the backups String path = CollectionHelper.getInstance().getCollectionPath(getActivity()); File[] files = BackupManager.getBackups(new File(path)); mBackups = new File[files.length]; for (int i = 0; i < files.length; i++) { mBackups[i] = files[files.length - 1 - i]; } if (mBackups.length == 0) { builder .title(res.getString(R.string.backup_restore)) .content(getMessage()) .positiveText(res.getString(R.string.dialog_ok)) .callback( new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog dialog) { ((DeckPicker) getActivity()).showDatabaseErrorDialog(DIALOG_ERROR_HANDLING); } }); } else { String[] dates = new String[mBackups.length]; for (int i = 0; i < mBackups.length; i++) { dates[i] = mBackups[i] .getName() .replaceAll(".*-(\\d{4}-\\d{2}-\\d{2})-(\\d{2})-(\\d{2}).apkg", "$1 ($2:$3 h)"); } builder .title(res.getString(R.string.backup_restore_select_title)) .negativeText(res.getString(R.string.dialog_cancel)) .callback( new MaterialDialog.ButtonCallback() { @Override public void onNegative(MaterialDialog dialog) { dismissAllDialogFragments(); } }) .items(dates) .itemsCallbackSingleChoice( dates.length, new MaterialDialog.ListCallbackSingleChoice() { @Override public boolean onSelection( MaterialDialog materialDialog, View view, int which, CharSequence charSequence) { if (mBackups[which].length() > 0) { // restore the backup if it's valid ((DeckPicker) getActivity()).restoreFromBackup(mBackups[which].getPath()); dismissAllDialogFragments(); } else { // otherwise show an error dialog new MaterialDialog.Builder(getActivity()) .title(R.string.backup_error) .content(R.string.backup_invalid_file_error) .positiveText(R.string.dialog_ok) .build() .show(); } return true; } }); } return builder.show(); case DIALOG_NEW_COLLECTION: // Allow user to create a new empty collection return builder .content(getMessage()) .positiveText(res.getString(R.string.dialog_positive_create)) .negativeText(res.getString(R.string.dialog_cancel)) .callback( new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog dialog) { CollectionHelper.getInstance().closeCollection(false); String path = CollectionHelper.getCollectionPath(getActivity()); if (BackupManager.moveDatabaseToBrokenFolder(path, false)) { ((DeckPicker) getActivity()).restartActivity(); } else { ((DeckPicker) getActivity()).showDatabaseErrorDialog(DIALOG_LOAD_FAILED); } } }) .show(); case DIALOG_CONFIRM_DATABASE_CHECK: // Confirmation dialog for database check return builder .content(getMessage()) .positiveText(res.getString(R.string.dialog_ok)) .negativeText(res.getString(R.string.dialog_cancel)) .callback( new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog dialog) { ((DeckPicker) getActivity()).integrityCheck(); dismissAllDialogFragments(); } }) .show(); case DIALOG_CONFIRM_RESTORE_BACKUP: // Confirmation dialog for backup restore return builder .content(getMessage()) .positiveText(res.getString(R.string.dialog_continue)) .negativeText(res.getString(R.string.dialog_cancel)) .callback( new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog dialog) { ((DeckPicker) getActivity()).showDatabaseErrorDialog(DIALOG_RESTORE_BACKUP); } }) .show(); case DIALOG_FULL_SYNC_FROM_SERVER: // Allow user to do a full-sync from the server return builder .content(getMessage()) .positiveText(res.getString(R.string.dialog_positive_overwrite)) .negativeText(res.getString(R.string.dialog_cancel)) .callback( new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog dialog) { ((DeckPicker) getActivity()).sync("download"); dismissAllDialogFragments(); } }) .show(); default: return null; } }