com.ichi2.libanki.test.ModelsTestCase.java Source code

Java tutorial

Introduction

Here is the source code for com.ichi2.libanki.test.ModelsTestCase.java

Source

/****************************************************************************************
 * Copyright (c) 2012 Kostas Spyropoulos <inigo.aldana@gmail.com>                       *
 *                                                                                      *
 * This program is free software; you can redistribute it and/or modify it under        *
 * the terms of the GNU General Public License as published by the Free Software        *
 * Foundation; either version 3 of the License, or (at your option) any later           *
 * version.                                                                             *
 *                                                                                      *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
 *                                                                                      *
 * You should have received a copy of the GNU General Public License along with         *
 * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
 ****************************************************************************************/
package com.ichi2.libanki.test;

import android.test.InstrumentationTestCase;
import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.MediumTest;

import com.ichi2.libanki.Card;
import com.ichi2.libanki.Collection;
import com.ichi2.libanki.Models;
import com.ichi2.libanki.Note;
import com.ichi2.libanki.Utils;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class ModelsTestCase extends InstrumentationTestCase {
    public ModelsTestCase(String name) {
        setName(name);
    }

    @MediumTest
    public void test_modelDelete() {
        Collection deck = Shared.getEmptyDeck(getInstrumentation().getContext());
        Note f = deck.newNote();
        f.setitem("Front", "1");
        f.setitem("Back", "2");
        deck.addNote(f);
        assertTrue(deck.cardCount() == 1);
        deck.getModels().rem(deck.getModels().current());
        assertTrue(deck.cardCount() == 0);
    }

    @MediumTest
    public void test_modelCopy() throws JSONException {
        Collection deck = Shared.getEmptyDeck(getInstrumentation().getContext());
        JSONObject m = deck.getModels().current();
        JSONObject m2 = deck.getModels().copy(m);
        assertEquals(m2.getString("name"), "Basic copy");
        MoreAsserts.assertNotEqual(m2.getInt("id"), m.getInt("id"));
        assertEquals(m2.getJSONArray("flds").length(), 2);
        assertEquals(m.getJSONArray("flds").length(), 2);
        assertEquals(m2.getJSONArray("flds").length(), m.getJSONArray("flds").length());
        assertEquals(m.getJSONArray("tmpls").length(), 1);
        assertEquals(m2.getJSONArray("tmpls").length(), 1);
        assertEquals(deck.getModels().scmhash(m), deck.getModels().scmhash(m2));
    }

    @MediumTest
    public void test_fields() {
        Collection d = Shared.getEmptyDeck(getInstrumentation().getContext());
        Note fct = d.newNote();
        fct.setitem("Front", "1");
        fct.setitem("Back", "2");
        d.addNote(fct);
        JSONObject m = d.getModels().current();
        // make sure renaming a field updates the templates
        try {
            d.getModels().renameField(m, m.getJSONArray("flds").getJSONObject(0), "NewFront");
            assertTrue(m.getJSONArray("tmpls").getJSONObject(0).getString("qfmt").contains("{{NewFront}}"));
            String h = d.getModels().scmhash(m);
            // add a field
            JSONObject f = d.getModels().newField(m.toString());
            f.put("name", "foo");
            d.getModels().addField(m, f);
            assertTrue(Arrays.equals(d.getNote(d.getModels().nids(m).get(0)).getFields(),
                    new String[] { "1", "2", "" }));
            assertTrue(!d.getModels().scmhash(m).equals(h));
            // rename it
            d.getModels().renameField(m, f, "bar");
            assertTrue(d.getNote(d.getModels().nids(m).get(0)).getitem("bar").equals(""));
            // delete back
            d.getModels().remField(m, m.getJSONArray("flds").getJSONObject(1));
            assertTrue(
                    Arrays.equals(d.getNote(d.getModels().nids(m).get(0)).getFields(), new String[] { "1", "" }));
            // move 0 -> 1
            d.getModels().moveField(m, m.getJSONArray("flds").getJSONObject(0), 1);
            assertTrue(
                    Arrays.equals(d.getNote(d.getModels().nids(m).get(0)).getFields(), new String[] { "", "1" }));
            // move 1 -> 0
            d.getModels().moveField(m, m.getJSONArray("flds").getJSONObject(1), 0);
            assertTrue(
                    Arrays.equals(d.getNote(d.getModels().nids(m).get(0)).getFields(), new String[] { "1", "" }));
            // add another and put in middle
            f = d.getModels().newField(m.toString());
            f.put("name", "baz");
            d.getModels().addField(m, f);
            fct = d.getNote(d.getModels().nids(m).get(0));
            fct.setitem("baz", "2");
            fct.flush();
            assertTrue(Arrays.equals(d.getNote(d.getModels().nids(m).get(0)).getFields(),
                    new String[] { "1", "", "2" }));
            // move 2 -> 1
            d.getModels().moveField(m, m.getJSONArray("flds").getJSONObject(2), 1);
            assertTrue(Arrays.equals(d.getNote(d.getModels().nids(m).get(0)).getFields(),
                    new String[] { "1", "2", "" }));
            // move 0 -> 2
            d.getModels().moveField(m, m.getJSONArray("flds").getJSONObject(0), 2);
            assertTrue(Arrays.equals(d.getNote(d.getModels().nids(m).get(0)).getFields(),
                    new String[] { "2", "", "1" }));
            // move 0 -> 1
            d.getModels().moveField(m, m.getJSONArray("flds").getJSONObject(0), 1);
            assertTrue(Arrays.equals(d.getNote(d.getModels().nids(m).get(0)).getFields(),
                    new String[] { "", "2", "1" }));
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
    }

    @MediumTest
    public void test_templates() {
        Collection d = Shared.getEmptyDeck(getInstrumentation().getContext());
        JSONObject m = d.getModels().current();
        Models mm = d.getModels();
        JSONObject t = mm.newTemplate("Reverse");
        try {
            t.put("qfmt", "{{Back}}");
            t.put("afmt", "{{Front}}");
            mm.addTemplate(m, t);
            mm.save();
            Note f = d.newNote();
            f.setitem("Front", "1");
            f.setitem("Back", "2");
            d.addNote(f);
            assertTrue(d.cardCount() == 2);
            Card c = f.cards().get(0);
            Card c2 = f.cards().get(1);
            // first card should have first ord
            assertTrue(c.getOrd() == 0);
            assertTrue(c2.getOrd() == 1);
            // switch templates
            d.getModels().moveTemplate(m, c.template(), 1);
            c.load();
            c2.load();
            assertTrue(c.getOrd() == 1);
            assertTrue(c2.getOrd() == 0);
            // removing a template should delete its cards
            assertTrue(d.getModels().remTemplate(m, m.getJSONArray("tmpls").getJSONObject(0)));
            assertTrue(d.cardCount() == 1);
            // and should have updated the other cards' ordinals
            c = f.cards().get(0);
            assertTrue(c.getOrd() == 0);
            assertTrue(Utils.stripHTML(c.getQuestion(false)).equals("1"));
            // it shouldn't be possible to orphan notes by removing templates
            t = mm.newTemplate(m.toString());
            mm.addTemplate(m, t);
            assertFalse(d.getModels().remTemplate(m, m.getJSONArray("tmpls").getJSONObject(0)));
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
    }

    @MediumTest
    public void test_text() {
        Collection d = Shared.getEmptyDeck(getInstrumentation().getContext());
        JSONObject m = d.getModels().current();
        try {
            m.getJSONArray("tmpls").getJSONObject(0).put("qfmt", "{{text:Front}}");
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
        d.getModels().save();
        Note f = d.newNote();
        f.setitem("Front", "hello<b>world");
        d.addNote(f);
        assertTrue(f.cards().get(0).getQuestion(false).contains("helloworld"));
    }

    @MediumTest
    public void test_cloze() {
        Collection d = Shared.getEmptyDeck(getInstrumentation().getContext());
        d.getModels().setCurrent(d.getModels().byName("Cloze"));
        Note f = d.newNote();
        try {
            assertTrue(f.model().getString("name").equals("Cloze"));
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
        // a cloze model with no clozes is not empty
        f.setitem("Text", "nothing");
        assertTrue(d.addNote(f) != 0);
        // try with one cloze
        f = d.newNote();
        f.setitem("Text", "hello {{c1::world}}");
        assertTrue(d.addNote(f) == 1);
        assertTrue(f.cards().get(0).getQuestion(false).contains("hello <span class=cloze>[...]</span>"));
        assertTrue(f.cards().get(0).getAnswer(false).contains("hello <span class=cloze>world</span>"));
        // and with a comment
        f = d.newNote();
        f.setitem("Text", "hello {{c1::world::typical}}");
        assertTrue(d.addNote(f) == 1);
        assertTrue(f.cards().get(0).getQuestion(false).contains("hello <span class=cloze>[typical]</span>"));
        assertTrue(f.cards().get(0).getAnswer(false).contains("hello <span class=cloze>world</span>"));
        // and with 2 clozes
        f = d.newNote();
        f.setitem("Text", "hello {{c1::world}} {{c2::bar}}");
        assertTrue(d.addNote(f) == 2);
        Card c1 = f.cards().get(0);
        Card c2 = f.cards().get(1);
        assertTrue(c1.getQuestion(false).contains("<span class=cloze>[...]</span> bar"));
        assertTrue(c1.getAnswer(false).contains("<span class=cloze>world</span> bar"));
        assertTrue(c2.getQuestion(false).contains("world <span class=cloze>[...]</span>"));
        assertTrue(c2.getAnswer(false).contains("world <span class=cloze>bar</span>"));
        // if there are multiple answers for a single cloze, they are given in a list
        f = d.newNote();
        f.setitem("Text", "a {{c1::b}} {{c1::c}}");
        assertTrue(d.addNote(f) == 1);
        assertTrue(f.cards().get(0).getAnswer(false)
                .contains("<span class=cloze>b</span> <span class=cloze>c</span>"));
        // if we add another cloze, a card should be generated
        int cnt = d.cardCount();
        f.setitem("Text", "{{c2::hello}} {{c1::foo}}");
        f.flush();
        assertTrue(d.cardCount() == cnt + 1);
        // 0 or negative indices are not supported
        f.setitem("Text", f.getitem("Text") + "{{c0::hello}} {{c-1::foo}}");
        f.flush();
        assertTrue(f.cards().size() == 2);
    }

    @MediumTest
    public void test_modelChange() {
        Collection deck = Shared.getEmptyDeck(getInstrumentation().getContext());
        JSONObject basic = deck.getModels().byName("Basic");
        JSONObject cloze = deck.getModels().byName("Cloze");
        // enable second template and add a note
        JSONObject m = deck.getModels().current();
        Models mm = deck.getModels();
        JSONObject t = mm.newTemplate("Reverse");
        try {
            t.put("qfmt", "{{Back}}");
            t.put("afmt", "{{Front}}");
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
        mm.addTemplate(m, t);
        mm.save(m);
        Note f = deck.newNote();
        f.setitem("Front", "f");
        f.setitem("Back", "b123");
        deck.addNote(f);
        // switch fields
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        map.put(0, 1);
        map.put(1, 0);
        deck.getModels().change(basic, new long[] { f.getId() }, basic, map, null);
        f.load();
        assertTrue(f.getitem("Front").equals("b123"));
        assertTrue(f.getitem("Back").equals("f"));
        // switch cards
        Card c0 = f.cards().get(0);
        Card c1 = f.cards().get(1);
        assertTrue(c0.getQuestion(false).contains("b123"));
        assertTrue(c1.getQuestion(false).contains("f"));
        assertTrue(c0.getOrd() == 0);
        assertTrue(c1.getOrd() == 1);
        deck.getModels().change(basic, new long[] { f.getId() }, basic, null, map);
        f.load();
        c0.load();
        c1.load();
        assertTrue(c0.getQuestion(false).contains("f"));
        assertTrue(c1.getQuestion(false).contains("b123"));
        assertTrue(c0.getOrd() == 1);
        assertTrue(c1.getOrd() == 0);
        // .cards returns cards in order
        assertTrue(f.cards().get(0).getId() == c1.getId());
        // delete first card
        map.put(0, null);
        map.put(1, 1);
        deck.getModels().change(basic, new long[] { f.getId() }, basic, null, map);
        f.load();
        assertTrue(c0.load());
        // the card was deleted. We don't throw exception as in anki, but return false
        assertFalse(c1.load());
        // but we have two cards, as one was generated
        assertTrue(f.cards().size() == 2);
        // an unmapped field becomes blank
        assertTrue(f.getitem("Front").equals("b123"));
        assertTrue(f.getitem("Back").equals("f"));
        deck.getModels().change(basic, new long[] { f.getId() }, basic, map, null);
        f.load();
        assertTrue(f.getitem("Front").equals(""));
        assertTrue(f.getitem("Back").equals("f"));
        // another note to try model conversion
        f = deck.newNote();
        f.setitem("Front", "f2");
        f.setitem("Back", "b2");
        deck.addNote(f);
        assertTrue(deck.getModels().useCount(basic) == 2);
        assertTrue(deck.getModels().useCount(cloze) == 0);
        map.put(0, 0);
        map.put(1, 1);
        deck.getModels().change(basic, new long[] { f.getId() }, cloze, map, map);
        f.load();
        assertTrue(f.getitem("Text").equals("f2"));
        assertTrue(f.cards().size() == 2);
        // back the other way, with deletion of second ord
        try {
            deck.getModels().remTemplate(basic, basic.getJSONArray("tmpls").getJSONObject(1));
            assertTrue(deck.getDb().queryScalar("SELECT count() FROM cards WHERE nid = " + f.getId()) == 2);
            deck.getModels().change(cloze, new long[] { f.getId() }, basic, map, map);
            assertTrue(deck.getDb().queryScalar("SELECT count() FROM cards WHERE nid = " + f.getId()) == 1);
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }

    }

    @MediumTest
    public void test_availOrds() {
        Collection d = Shared.getEmptyDeck(getInstrumentation().getContext());
        JSONObject m = d.getModels().current();
        Models mm = d.getModels();
        try {
            JSONObject t = m.getJSONArray("tmpls").getJSONObject(0);
            Note f = d.newNote();
            f.setitem("Front", "1");
            // simple templates
            assertTrue(Arrays.equals(mm.availOrds(m, Utils.joinFields(f.getFields())).toArray(new Integer[] {}),
                    new Integer[] { 0 }));
            t.put("qfmt", "{{Back}}");
            mm.save(m, true);
            assertTrue(mm.availOrds(m, Utils.joinFields(f.getFields())).isEmpty());
            // AND
            t.put("qfmt", "{{#Front}}{{#Back}}{{Front}}{{/Back}}{{/Front}}");
            mm.save(m, true);
            assertTrue(mm.availOrds(m, Utils.joinFields(f.getFields())).isEmpty());
            t.put("qfmt", "{{#Front}}\n{{#Back}}\n{{Front}}\n{{/Back}}\n{{/Front}}");
            mm.save(m, true);
            assertTrue(mm.availOrds(m, Utils.joinFields(f.getFields())).isEmpty());
            // OR
            t.put("qfmt", "{{Front}}\n{{Back}}");
            mm.save(m, true);
            assertTrue(Arrays.equals(mm.availOrds(m, Utils.joinFields(f.getFields())).toArray(new Integer[] {}),
                    new Integer[] { 0 }));
            t.put("Front", "");
            t.put("Back", "1");
            assertTrue(Arrays.equals(mm.availOrds(m, Utils.joinFields(f.getFields())).toArray(new Integer[] {}),
                    new Integer[] { 0 }));
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
    }
}