Skip to content

Instantly share code, notes, and snippets.

@xian
Forked from JakeWharton/ActionBarSherlockRobolectric.java
Last active December 14, 2015 07:48

Revisions

  1. xian revised this gist Aug 16, 2013. 3 changed files with 3 additions and 3 deletions.
    2 changes: 1 addition & 1 deletion ActionBarSherlockRobolectric.java
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    // Copyright 2012 Square, Inc.
    // Copyright 2012 Square, Inc. License: Apache 2.
    package com.squareup.test;

    import android.app.Activity;
    2 changes: 1 addition & 1 deletion MockActionBar.java
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    // Copyright 2012 Square, Inc.
    // Copyright 2012 Square, Inc. License: Apache 2.
    package com.squareup.test;

    import android.content.Context;
    2 changes: 1 addition & 1 deletion SherlockMenuInflater.java
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    // Copyright 2012 Square, Inc.
    // Copyright 2012 Square, Inc. License: Apache 2.
    package com.squareup.test.actionbarsherlock;

    import android.content.Context;
  2. xian revised this gist Feb 28, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion 00_README.md
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    You need to do this once in your base test runner:
    You need to add the files below, and do this once in your base test runner:

    ```java
    ActionBarSherlock.registerImplementation(ActionBarSherlockRobolectric.class);
  3. xian renamed this gist Feb 28, 2013. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  4. xian renamed this gist Feb 28, 2013. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  5. xian revised this gist Feb 28, 2013. No changes.
  6. xian revised this gist Feb 27, 2013. 8 changed files with 59 additions and 605 deletions.
    11 changes: 10 additions & 1 deletion ActionBarSherlockRobolectric.java
    Original file line number Diff line number Diff line change
    @@ -9,8 +9,10 @@
    import com.actionbarsherlock.app.ActionBar;
    import com.actionbarsherlock.internal.ActionBarSherlockCompat;
    import com.actionbarsherlock.internal.ActionBarSherlockNative;
    import com.actionbarsherlock.view.MenuInflater;
    import com.squareup.test.actionbarsherlock.SherlockMenuInflater;

    import static com.xtremelabs.robolectric.Robolectric.shadowOf;
    import static org.robolectric.Robolectric.shadowOf;

    /**
    * During initialization, {@link ActionBarSherlock} figures out which {@link
    @@ -49,4 +51,11 @@ public ActionBarSherlockRobolectric(Activity activity, int flags) {
    @Override protected Context getThemedContext() {
    return mActivity;
    }

    @Override public MenuInflater getMenuInflater() {
    if (mMenuInflater == null) {
    mMenuInflater = new SherlockMenuInflater(mActivity);
    }
    return mMenuInflater;
    }
    }
    57 changes: 0 additions & 57 deletions ActionBarSherlockTestRunner.java
    Original file line number Diff line number Diff line change
    @@ -1,57 +0,0 @@
    // Copyright 2012 Square, Inc.
    package com.squareup.test;

    import com.squareup.test.actionbarsherlock.SherlockResourceLoader;
    import com.xtremelabs.robolectric.RobolectricConfig;
    import com.xtremelabs.robolectric.RobolectricTestRunner;
    import com.xtremelabs.robolectric.bytecode.RobolectricClassLoader;
    import com.xtremelabs.robolectric.bytecode.ShadowWrangler;
    import com.xtremelabs.robolectric.res.ResourceLoader;
    import java.util.Arrays;
    import org.junit.runners.model.InitializationError;

    /**
    * {@link RobolectricTestRunner} primarily used to provide a custom {@link
    * RobolectricClassLoader} for instrumenting our own packages. It also provides a
    * custom {@link ResourceLoader} to load ActionBarSherlock menus.
    */
    public class ActionBarSherlockTestRunner extends RobolectricTestRunner {

    private static RobolectricClassLoader classLoader;

    protected ActionBarSherlockTestRunner(Class<?> testClass, RobolectricConfig robolectricConfig)
    throws InitializationError {
    super(testClass, ShadowWrangler.getInstance(), getClassLoader(), robolectricConfig);
    }

    /** Use this to add more packages that you want to be instrumented by Robolectric. */
    private static RobolectricClassLoader getClassLoader() {
    // Please don't cry.
    if (classLoader == null && !isInstrumented()) {
    classLoader = new RobolectricClassLoader(ShadowWrangler.getInstance(),
    Arrays.asList("com.actionbarsherlock.app", "com.actionbarsherlock.view"));
    }
    return classLoader;
    }

    /** Return a {@link SherlockResourceLoader} instead, which loads ABS menus for testing. */
    @Override protected ResourceLoader createResourceLoader(RobolectricConfig robolectricConfig) {
    ResourceLoader resourceLoader = resourceLoaderForRootAndDirectory.get(robolectricConfig);
    if (resourceLoader == null) {
    try {
    robolectricConfig.validate();

    String rClassName = robolectricConfig.getRClassName();
    Class rClass = Class.forName(rClassName);
    resourceLoader = new SherlockResourceLoader(robolectricConfig.getRealSdkVersion(), rClass,
    robolectricConfig.getResourcePath(), robolectricConfig.getAssetsDirectory());
    resourceLoaderForRootAndDirectory.put(robolectricConfig, resourceLoader);
    } catch (Exception e) {
    throw new RuntimeException(e);
    }
    }

    resourceLoader.setStrictI18n(robolectricConfig.getStrictI18n());
    return resourceLoader;
    }
    }
    8 changes: 3 additions & 5 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -2,8 +2,6 @@ You need to do this once in your base test runner:

    ```java
    ActionBarSherlock.registerImplementation(ActionBarSherlockRobolectric.class);
    ```

    and your Robolectric should have this patch:

    https://github.com/square/robolectric/commit/fac24e5ef6d5830e6e67ad27aff2f8f57cedb5a1
    ActionBarSherlock.unregisterImplementation(ActionBarSherlockNative.class);
    ActionBarSherlock.unregisterImplementation(ActionBarSherlockCompat.class);
    ```
    29 changes: 0 additions & 29 deletions ShadowSherlockFragmentActivity.java
    Original file line number Diff line number Diff line change
    @@ -1,29 +0,0 @@
    // Copyright 2012 Square, Inc.
    package com.squareup.test.actionbarsherlock;

    import com.actionbarsherlock.app.ActionBar;
    import com.actionbarsherlock.app.SherlockFragmentActivity;
    import com.actionbarsherlock.view.MenuInflater;
    import com.squareup.test.MockActionBar;
    import com.xtremelabs.robolectric.internal.Implementation;
    import com.xtremelabs.robolectric.internal.Implements;
    import com.xtremelabs.robolectric.shadows.ShadowFragmentActivity;

    @Implements(SherlockFragmentActivity.class)
    public class ShadowSherlockFragmentActivity extends ShadowFragmentActivity {

    private ActionBar actionBar;

    @Implementation
    public ActionBar getSupportActionBar() {
    if (actionBar == null) {
    actionBar = new MockActionBar(getApplicationContext());
    }
    return actionBar;
    }

    @Implementation
    public MenuInflater getSupportMenuInflater() {
    return new SherlockMenuInflater(getApplicationContext());
    }
    }
    53 changes: 46 additions & 7 deletions SherlockMenuInflater.java
    Original file line number Diff line number Diff line change
    @@ -2,27 +2,66 @@
    package com.squareup.test.actionbarsherlock;

    import android.content.Context;
    import android.util.AttributeSet;
    import com.actionbarsherlock.view.Menu;
    import com.actionbarsherlock.view.MenuInflater;
    import com.actionbarsherlock.view.SubMenu;
    import org.robolectric.internal.Implementation;
    import org.robolectric.res.MenuNode;
    import org.robolectric.res.ResName;
    import org.robolectric.res.ResourceLoader;
    import org.robolectric.util.I18nException;

    import static com.xtremelabs.robolectric.Robolectric.shadowOf;
    import static org.robolectric.Robolectric.shadowOf;

    /**
    * Inflates menus that are part of ActionBarSherlock instead. Uses ABS custom {@link
    * com.actionbarsherlock.app.view.Menu} instead of the stock one.
    * com.actionbarsherlock.view.Menu} instead of the stock one.
    */
    public class SherlockMenuInflater extends MenuInflater {

    private final Context context;

    public SherlockMenuInflater(Context context) {
    super(context);
    this.context = context;
    }

    @Override public void inflate(int menuRes, Menu menu) {
    SherlockResourceLoader sherlockResourceLoader =
    (SherlockResourceLoader) shadowOf(context.getApplicationContext()).getResourceLoader();
    sherlockResourceLoader.inflateSherlockMenu(context, menuRes, menu);
    @Implementation
    public void inflate(int resource, Menu root) {
    String qualifiers = shadowOf(context.getResources().getConfiguration()).getQualifiers();
    ResourceLoader resourceLoader = shadowOf(context).getResourceLoader();
    ResName resName = shadowOf(context).getResName(resource);
    MenuNode menuNode = resourceLoader.getMenuNode(resName, qualifiers);

    try {
    addChildrenInGroup(menuNode, 0, root);
    } catch (I18nException e) {
    throw e;
    } catch (Exception e) {
    throw new RuntimeException("error inflating " + resName, e);
    }
    }

    private void addChildrenInGroup(MenuNode source, int groupId, Menu root) {
    for (MenuNode child : source.getChildren()) {
    String name = child.getName();
    AttributeSet attributes = shadowOf(context).createAttributeSet(child.getAttributes(), null);
    if (name.equals("item")) {
    if (child.isSubMenuItem()) {
    SubMenu sub = root.addSubMenu(groupId,
    attributes.getAttributeResourceValue("android", "id", 0),
    0, attributes.getAttributeValue("android", "title"));
    MenuNode subMenuNode = child.getChildren().get(0);
    addChildrenInGroup(subMenuNode, groupId, sub);
    } else {
    root.add(groupId,
    attributes.getAttributeResourceValue("android", "id", 0),
    0, attributes.getAttributeValue("android", "title"));
    }
    } else if (name.equals("group")) {
    int newGroupId = attributes.getAttributeResourceValue("android", "id", 0);
    addChildrenInGroup(child, newGroupId, root);
    }
    }
    }
    }
    196 changes: 0 additions & 196 deletions SherlockMenuLoader.java
    Original file line number Diff line number Diff line change
    @@ -1,196 +0,0 @@
    // Copyright 2012 Square, Inc.
    package com.squareup.test.actionbarsherlock;

    import com.actionbarsherlock.view.Menu;
    import com.actionbarsherlock.view.MenuItem;
    import com.actionbarsherlock.view.SubMenu;
    import com.xtremelabs.robolectric.res.AttrResourceLoader;
    import com.xtremelabs.robolectric.res.ResourceExtractor;
    import com.xtremelabs.robolectric.res.XmlLoader;
    import java.io.File;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;

    import org.w3c.dom.Document;
    import org.w3c.dom.NamedNodeMap;
    import org.w3c.dom.Node;
    import org.w3c.dom.NodeList;

    import android.content.Context;
    import android.text.TextUtils;

    import com.xtremelabs.robolectric.tester.android.util.TestAttributeSet;
    import com.xtremelabs.robolectric.util.I18nException;

    /**
    * Exact copy of the original {@link com.xtremelabs.robolectric.res.MenuLoader} provided by
    * Robolectric, but instead uses the ABS {@link MenuItem} and the ABS {@link Menu}.
    */
    public class SherlockMenuLoader extends XmlLoader {
    private Map<String, MenuNode> menuNodesByMenuName = new HashMap<String, MenuNode>();
    private AttrResourceLoader attrResourceLoader;

    public SherlockMenuLoader(ResourceExtractor resourceExtractor,
    AttrResourceLoader attrResourceLoader) {
    super(resourceExtractor);
    this.attrResourceLoader = attrResourceLoader;
    }

    @Override
    protected void processResourceXml(File xmlFile, Document document, boolean ignored)
    throws Exception {
    MenuNode topLevelNode = new MenuNode("top-level", new HashMap<String, String>());

    NodeList items = document.getChildNodes();
    if (items.getLength() != 1) {
    throw new RuntimeException(
    "Expected only one top-level item in menu file " + xmlFile.getName());
    }
    if (items.item(0).getNodeName().compareTo("menu") != 0) {
    throw new RuntimeException(
    "Expected a top-level item called 'menu' in menu file " + xmlFile.getName());
    }

    processChildren(items.item(0).getChildNodes(), topLevelNode);
    menuNodesByMenuName.put("menu/" + xmlFile.getName().replace(".xml", ""), topLevelNode);
    }

    private void processChildren(NodeList childNodes, MenuNode parent) {
    for (int i = 0; i < childNodes.getLength(); i++) {
    Node node = childNodes.item(i);
    processNode(node, parent);
    }
    }

    private void processNode(Node node, MenuNode parent) {
    String name = node.getNodeName();
    NamedNodeMap attributes = node.getAttributes();
    Map<String, String> attrMap = new HashMap<String, String>();
    if (attributes != null) {
    int length = attributes.getLength();
    for (int i = 0; i < length; i++) {
    Node attr = attributes.item(i);
    attrMap.put(attr.getNodeName(), attr.getNodeValue());
    }
    }

    if (!name.startsWith("#")) {
    MenuNode menuNode = new MenuNode(name, attrMap);
    parent.addChild(menuNode);
    NodeList children = node.getChildNodes();
    if (children != null && children.getLength() != 0) {
    for (int i = 0; i < children.getLength(); i++) {
    Node nodei = children.item(i);
    if (childToIgnore(nodei)) {
    continue;
    } else if (validChildren(nodei)) {
    // recursively add all nodes
    processNode(nodei, menuNode);
    } else {
    throw new RuntimeException("Unknown menu node" + nodei.getNodeName());
    }
    }
    }
    }
    }

    private static boolean childToIgnore(Node nodei) {
    return TextUtils.isEmpty(nodei.getNodeName()) || nodei.getNodeName().startsWith("#");
    }

    private static boolean validChildren(Node nodei) {
    return nodei.getNodeName().equals("item")
    || nodei.getNodeName().equals("menu")
    || nodei.getNodeName().equals("group");
    }

    public boolean inflateMenu(Context context, String key, Menu root) {
    return inflateMenu(context, key, null, root);
    }

    public boolean inflateMenu(Context context, int resourceId, Menu root) {
    return inflateMenu(context, resourceExtractor.getResourceName(resourceId), root);
    }

    private boolean inflateMenu(Context context, String key, Map<String, String> attributes,
    Menu root) {
    MenuNode menuNode = menuNodesByMenuName.get(key);
    if (menuNode == null) return false;

    try {
    if (attributes != null) {
    for (Map.Entry<String, String> entry : attributes.entrySet()) {
    if (!entry.getKey().equals("menu")) {
    menuNode.attributes.put(entry.getKey(), entry.getValue());
    }
    }
    }
    menuNode.inflate(context, root);
    return true;
    } catch (I18nException e) {
    throw e;
    } catch (Exception e) {
    throw new RuntimeException("error inflating " + key, e);
    }
    }

    public class MenuNode {
    private String name;
    private final TestAttributeSet attributes;

    private List<MenuNode> children = new ArrayList<MenuNode>();

    public MenuNode(String name, Map<String, String> attributes) {
    this.name = name;
    this.attributes =
    new TestAttributeSet(attributes, resourceExtractor, attrResourceLoader, null, false);
    }

    public List<MenuNode> getChildren() {
    return children;
    }

    public void addChild(MenuNode MenuNode) {
    children.add(MenuNode);
    }

    private boolean isSubMenuItem(MenuNode child) {
    //List<MenuLoader.MenuNode> ch = child.children;
    //return ch != null && ch.size() == 1 && "menu".equals(ch.get(0).name);
    // TODO Must fix to support sub menu items.
    return false;
    }

    private void addChildrenInGroup(MenuNode source, int groupId, Menu root) {
    for (MenuNode child : source.children) {
    String name = child.name;
    TestAttributeSet attributes = child.attributes;
    if (strictI18n) {
    attributes.validateStrictI18n();
    }
    if (name.equals("item")) {
    if (isSubMenuItem(child)) {
    SubMenu sub =
    root.addSubMenu(groupId, attributes.getAttributeResourceValue("android", "id", 0),
    0, attributes.getAttributeValue("android", "title"));
    MenuNode subMenuNode = child.children.get(0);
    addChildrenInGroup(subMenuNode, groupId, sub);
    } else {
    MenuItem menuItem =
    root.add(groupId, attributes.getAttributeResourceValue("android", "id", 0), 0,
    attributes.getAttributeValue("android", "title"));
    }
    } else if (name.equals("group")) {
    int newGroupId = attributes.getAttributeResourceValue("android", "id", 0);
    addChildrenInGroup(child, newGroupId, root);
    }
    }
    }

    public void inflate(Context context, Menu root) throws Exception {
    addChildrenInGroup(this, 0, root);
    }
    }
    }
    52 changes: 0 additions & 52 deletions SherlockResourceLoader.java
    Original file line number Diff line number Diff line change
    @@ -1,52 +0,0 @@
    // Copyright 2012 Square, Inc.
    package com.squareup.test.actionbarsherlock;

    import android.content.Context;
    import com.actionbarsherlock.view.Menu;
    import com.xtremelabs.robolectric.res.AttrResourceLoader;
    import com.xtremelabs.robolectric.res.DocumentLoader;
    import com.xtremelabs.robolectric.res.ResourceExtractor;
    import com.xtremelabs.robolectric.res.ResourceLoader;
    import java.io.File;
    import java.io.FileFilter;
    import java.util.List;

    /**
    * Custom resource loader for ActionBarSherlock. It extends the original {@link ResourceLoader}.
    * At this time we just load all menus into a {@link SherlockMenuLoader}.
    */
    public class SherlockResourceLoader extends ResourceLoader {

    private SherlockMenuLoader sherlockMenuLoader;

    public SherlockResourceLoader(int sdkVersion, Class rClass, List<File> resourcePath,
    File assetsDir) throws Exception {
    super(sdkVersion, rClass, resourcePath, assetsDir);
    ResourceExtractor resourceExtractor = new ResourceExtractor();
    try {
    resourceExtractor.addLocalRClass(rClass);
    resourceExtractor.addSystemRClass(android.R.class);
    } catch (Exception e) {
    throw new RuntimeException(e);
    }
    sherlockMenuLoader =
    new SherlockMenuLoader(resourceExtractor, new AttrResourceLoader(resourceExtractor));
    }

    @Override protected void loadOtherResources(File resourceDir) {
    DocumentLoader loader = new DocumentLoader(sherlockMenuLoader);
    try {
    loader.loadResourceXmlDirs(resourceDir.listFiles(new FileFilter() {
    @Override public boolean accept(File file) {
    return file.getPath().contains(File.separator + "menu");
    }
    }));
    } catch (Exception e) {
    throw new RuntimeException(e);
    }
    }

    public void inflateSherlockMenu(Context context, int resource, Menu root) {
    sherlockMenuLoader.inflateMenu(context, resource, root);
    }
    }
    258 changes: 0 additions & 258 deletions TestSherlockMenuItem.java
    Original file line number Diff line number Diff line change
    @@ -1,258 +0,0 @@
    package com.squareup.test.actionbarsherlock;

    import android.content.Intent;
    import android.graphics.drawable.Drawable;
    import android.view.ContextMenu;
    import android.view.View;
    import com.actionbarsherlock.view.ActionProvider;
    import com.actionbarsherlock.view.MenuItem;
    import com.actionbarsherlock.view.SubMenu;
    import com.xtremelabs.robolectric.Robolectric;

    /**
    * Copied from {@link com.xtremelabs.robolectric.tester.android.view.TestMenuItem}.
    */
    public class TestSherlockMenuItem implements MenuItem {

    private int itemId;
    private CharSequence title;
    private boolean enabled = true;
    private OnMenuItemClickListener menuItemClickListener;
    public int iconRes;
    private Intent intent;
    private SubMenu subMenu;

    public TestSherlockMenuItem() {
    super();
    }

    public TestSherlockMenuItem(int itemId) {
    super();
    this.itemId = itemId;
    }

    public void setItemId(int itemId) {
    this.itemId = itemId;
    }

    @Override
    public int getItemId() {
    return itemId;
    }

    @Override
    public int getGroupId() {
    return 0;
    }

    @Override
    public int getOrder() {
    return 0;
    }

    @Override
    public MenuItem setTitle(CharSequence title) {
    this.title = title;
    return this;
    }

    @Override
    public MenuItem setTitle(int title) {
    return null;
    }

    @Override
    public CharSequence getTitle() {
    return title;
    }

    @Override
    public MenuItem setTitleCondensed(CharSequence title) {
    return null;
    }

    @Override
    public CharSequence getTitleCondensed() {
    return null;
    }

    @Override
    public MenuItem setIcon(Drawable icon) {
    return null;
    }

    @Override
    public MenuItem setIcon(int iconRes) {
    this.iconRes = iconRes;
    return this;
    }

    @Override
    public Drawable getIcon() {
    return null;
    }

    @Override
    public MenuItem setIntent(Intent intent) {
    this.intent = intent;
    return this;
    }

    @Override
    public Intent getIntent() {
    return this.intent;
    }

    @Override
    public MenuItem setShortcut(char numericChar, char alphaChar) {
    return null;
    }

    @Override
    public MenuItem setNumericShortcut(char numericChar) {
    return null;
    }

    @Override
    public char getNumericShortcut() {
    return 0;
    }

    @Override
    public MenuItem setAlphabeticShortcut(char alphaChar) {
    return null;
    }

    @Override
    public char getAlphabeticShortcut() {
    return 0;
    }

    @Override
    public MenuItem setCheckable(boolean checkable) {
    return null;
    }

    @Override
    public boolean isCheckable() {
    return false;
    }

    @Override
    public MenuItem setChecked(boolean checked) {
    return null;
    }

    @Override
    public boolean isChecked() {
    return false;
    }

    @Override
    public MenuItem setVisible(boolean visible) {
    return null;
    }

    @Override
    public boolean isVisible() {
    return false;
    }

    @Override
    public MenuItem setEnabled(boolean enabled) {
    this.enabled = enabled;
    return this;
    }

    @Override
    public boolean isEnabled() {
    return enabled;
    }

    @Override
    public boolean hasSubMenu() {
    return subMenu != null;
    }

    @Override
    public SubMenu getSubMenu() {
    return subMenu;
    }

    public void setSubMenu(SubMenu subMenu) {
    this.subMenu = subMenu;
    }

    @Override
    public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) {
    this.menuItemClickListener = menuItemClickListener;
    return this;
    }

    @Override
    public ContextMenu.ContextMenuInfo getMenuInfo() {
    return null;
    }

    @Override
    public void setShowAsAction(int i) {
    }

    @Override
    public MenuItem setShowAsActionFlags(int i) {
    return null;
    }

    @Override
    public MenuItem setActionView(View view) {
    return null;
    }

    @Override
    public MenuItem setActionView(int i) {
    return null;
    }

    @Override
    public View getActionView() {
    return null;
    }

    @Override
    public MenuItem setActionProvider(ActionProvider actionProvider) {
    return null;
    }

    @Override
    public ActionProvider getActionProvider() {
    return null;
    }

    @Override
    public boolean expandActionView() {
    return false;
    }

    @Override
    public boolean collapseActionView() {
    return false;
    }

    @Override
    public boolean isActionViewExpanded() {
    return false;
    }

    @Override
    public MenuItem setOnActionExpandListener(OnActionExpandListener onActionExpandListener) {
    return null;
    }

    public void click() {
    if (enabled && menuItemClickListener != null) {
    menuItemClickListener.onMenuItemClick(this);
    } else if (enabled && intent != null) {
    Robolectric.application.startActivity(intent);
    }
    }
    }
  7. @JakeWharton JakeWharton revised this gist Sep 29, 2012. 5 changed files with 563 additions and 0 deletions.
    29 changes: 29 additions & 0 deletions ShadowSherlockFragmentActivity.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,29 @@
    // Copyright 2012 Square, Inc.
    package com.squareup.test.actionbarsherlock;

    import com.actionbarsherlock.app.ActionBar;
    import com.actionbarsherlock.app.SherlockFragmentActivity;
    import com.actionbarsherlock.view.MenuInflater;
    import com.squareup.test.MockActionBar;
    import com.xtremelabs.robolectric.internal.Implementation;
    import com.xtremelabs.robolectric.internal.Implements;
    import com.xtremelabs.robolectric.shadows.ShadowFragmentActivity;

    @Implements(SherlockFragmentActivity.class)
    public class ShadowSherlockFragmentActivity extends ShadowFragmentActivity {

    private ActionBar actionBar;

    @Implementation
    public ActionBar getSupportActionBar() {
    if (actionBar == null) {
    actionBar = new MockActionBar(getApplicationContext());
    }
    return actionBar;
    }

    @Implementation
    public MenuInflater getSupportMenuInflater() {
    return new SherlockMenuInflater(getApplicationContext());
    }
    }
    28 changes: 28 additions & 0 deletions SherlockMenuInflater.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,28 @@
    // Copyright 2012 Square, Inc.
    package com.squareup.test.actionbarsherlock;

    import android.content.Context;
    import com.actionbarsherlock.view.Menu;
    import com.actionbarsherlock.view.MenuInflater;

    import static com.xtremelabs.robolectric.Robolectric.shadowOf;

    /**
    * Inflates menus that are part of ActionBarSherlock instead. Uses ABS custom {@link
    * com.actionbarsherlock.app.view.Menu} instead of the stock one.
    */
    public class SherlockMenuInflater extends MenuInflater {

    private final Context context;

    public SherlockMenuInflater(Context context) {
    super(context);
    this.context = context;
    }

    @Override public void inflate(int menuRes, Menu menu) {
    SherlockResourceLoader sherlockResourceLoader =
    (SherlockResourceLoader) shadowOf(context.getApplicationContext()).getResourceLoader();
    sherlockResourceLoader.inflateSherlockMenu(context, menuRes, menu);
    }
    }
    196 changes: 196 additions & 0 deletions SherlockMenuLoader.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,196 @@
    // Copyright 2012 Square, Inc.
    package com.squareup.test.actionbarsherlock;

    import com.actionbarsherlock.view.Menu;
    import com.actionbarsherlock.view.MenuItem;
    import com.actionbarsherlock.view.SubMenu;
    import com.xtremelabs.robolectric.res.AttrResourceLoader;
    import com.xtremelabs.robolectric.res.ResourceExtractor;
    import com.xtremelabs.robolectric.res.XmlLoader;
    import java.io.File;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;

    import org.w3c.dom.Document;
    import org.w3c.dom.NamedNodeMap;
    import org.w3c.dom.Node;
    import org.w3c.dom.NodeList;

    import android.content.Context;
    import android.text.TextUtils;

    import com.xtremelabs.robolectric.tester.android.util.TestAttributeSet;
    import com.xtremelabs.robolectric.util.I18nException;

    /**
    * Exact copy of the original {@link com.xtremelabs.robolectric.res.MenuLoader} provided by
    * Robolectric, but instead uses the ABS {@link MenuItem} and the ABS {@link Menu}.
    */
    public class SherlockMenuLoader extends XmlLoader {
    private Map<String, MenuNode> menuNodesByMenuName = new HashMap<String, MenuNode>();
    private AttrResourceLoader attrResourceLoader;

    public SherlockMenuLoader(ResourceExtractor resourceExtractor,
    AttrResourceLoader attrResourceLoader) {
    super(resourceExtractor);
    this.attrResourceLoader = attrResourceLoader;
    }

    @Override
    protected void processResourceXml(File xmlFile, Document document, boolean ignored)
    throws Exception {
    MenuNode topLevelNode = new MenuNode("top-level", new HashMap<String, String>());

    NodeList items = document.getChildNodes();
    if (items.getLength() != 1) {
    throw new RuntimeException(
    "Expected only one top-level item in menu file " + xmlFile.getName());
    }
    if (items.item(0).getNodeName().compareTo("menu") != 0) {
    throw new RuntimeException(
    "Expected a top-level item called 'menu' in menu file " + xmlFile.getName());
    }

    processChildren(items.item(0).getChildNodes(), topLevelNode);
    menuNodesByMenuName.put("menu/" + xmlFile.getName().replace(".xml", ""), topLevelNode);
    }

    private void processChildren(NodeList childNodes, MenuNode parent) {
    for (int i = 0; i < childNodes.getLength(); i++) {
    Node node = childNodes.item(i);
    processNode(node, parent);
    }
    }

    private void processNode(Node node, MenuNode parent) {
    String name = node.getNodeName();
    NamedNodeMap attributes = node.getAttributes();
    Map<String, String> attrMap = new HashMap<String, String>();
    if (attributes != null) {
    int length = attributes.getLength();
    for (int i = 0; i < length; i++) {
    Node attr = attributes.item(i);
    attrMap.put(attr.getNodeName(), attr.getNodeValue());
    }
    }

    if (!name.startsWith("#")) {
    MenuNode menuNode = new MenuNode(name, attrMap);
    parent.addChild(menuNode);
    NodeList children = node.getChildNodes();
    if (children != null && children.getLength() != 0) {
    for (int i = 0; i < children.getLength(); i++) {
    Node nodei = children.item(i);
    if (childToIgnore(nodei)) {
    continue;
    } else if (validChildren(nodei)) {
    // recursively add all nodes
    processNode(nodei, menuNode);
    } else {
    throw new RuntimeException("Unknown menu node" + nodei.getNodeName());
    }
    }
    }
    }
    }

    private static boolean childToIgnore(Node nodei) {
    return TextUtils.isEmpty(nodei.getNodeName()) || nodei.getNodeName().startsWith("#");
    }

    private static boolean validChildren(Node nodei) {
    return nodei.getNodeName().equals("item")
    || nodei.getNodeName().equals("menu")
    || nodei.getNodeName().equals("group");
    }

    public boolean inflateMenu(Context context, String key, Menu root) {
    return inflateMenu(context, key, null, root);
    }

    public boolean inflateMenu(Context context, int resourceId, Menu root) {
    return inflateMenu(context, resourceExtractor.getResourceName(resourceId), root);
    }

    private boolean inflateMenu(Context context, String key, Map<String, String> attributes,
    Menu root) {
    MenuNode menuNode = menuNodesByMenuName.get(key);
    if (menuNode == null) return false;

    try {
    if (attributes != null) {
    for (Map.Entry<String, String> entry : attributes.entrySet()) {
    if (!entry.getKey().equals("menu")) {
    menuNode.attributes.put(entry.getKey(), entry.getValue());
    }
    }
    }
    menuNode.inflate(context, root);
    return true;
    } catch (I18nException e) {
    throw e;
    } catch (Exception e) {
    throw new RuntimeException("error inflating " + key, e);
    }
    }

    public class MenuNode {
    private String name;
    private final TestAttributeSet attributes;

    private List<MenuNode> children = new ArrayList<MenuNode>();

    public MenuNode(String name, Map<String, String> attributes) {
    this.name = name;
    this.attributes =
    new TestAttributeSet(attributes, resourceExtractor, attrResourceLoader, null, false);
    }

    public List<MenuNode> getChildren() {
    return children;
    }

    public void addChild(MenuNode MenuNode) {
    children.add(MenuNode);
    }

    private boolean isSubMenuItem(MenuNode child) {
    //List<MenuLoader.MenuNode> ch = child.children;
    //return ch != null && ch.size() == 1 && "menu".equals(ch.get(0).name);
    // TODO Must fix to support sub menu items.
    return false;
    }

    private void addChildrenInGroup(MenuNode source, int groupId, Menu root) {
    for (MenuNode child : source.children) {
    String name = child.name;
    TestAttributeSet attributes = child.attributes;
    if (strictI18n) {
    attributes.validateStrictI18n();
    }
    if (name.equals("item")) {
    if (isSubMenuItem(child)) {
    SubMenu sub =
    root.addSubMenu(groupId, attributes.getAttributeResourceValue("android", "id", 0),
    0, attributes.getAttributeValue("android", "title"));
    MenuNode subMenuNode = child.children.get(0);
    addChildrenInGroup(subMenuNode, groupId, sub);
    } else {
    MenuItem menuItem =
    root.add(groupId, attributes.getAttributeResourceValue("android", "id", 0), 0,
    attributes.getAttributeValue("android", "title"));
    }
    } else if (name.equals("group")) {
    int newGroupId = attributes.getAttributeResourceValue("android", "id", 0);
    addChildrenInGroup(child, newGroupId, root);
    }
    }
    }

    public void inflate(Context context, Menu root) throws Exception {
    addChildrenInGroup(this, 0, root);
    }
    }
    }
    52 changes: 52 additions & 0 deletions SherlockResourceLoader.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,52 @@
    // Copyright 2012 Square, Inc.
    package com.squareup.test.actionbarsherlock;

    import android.content.Context;
    import com.actionbarsherlock.view.Menu;
    import com.xtremelabs.robolectric.res.AttrResourceLoader;
    import com.xtremelabs.robolectric.res.DocumentLoader;
    import com.xtremelabs.robolectric.res.ResourceExtractor;
    import com.xtremelabs.robolectric.res.ResourceLoader;
    import java.io.File;
    import java.io.FileFilter;
    import java.util.List;

    /**
    * Custom resource loader for ActionBarSherlock. It extends the original {@link ResourceLoader}.
    * At this time we just load all menus into a {@link SherlockMenuLoader}.
    */
    public class SherlockResourceLoader extends ResourceLoader {

    private SherlockMenuLoader sherlockMenuLoader;

    public SherlockResourceLoader(int sdkVersion, Class rClass, List<File> resourcePath,
    File assetsDir) throws Exception {
    super(sdkVersion, rClass, resourcePath, assetsDir);
    ResourceExtractor resourceExtractor = new ResourceExtractor();
    try {
    resourceExtractor.addLocalRClass(rClass);
    resourceExtractor.addSystemRClass(android.R.class);
    } catch (Exception e) {
    throw new RuntimeException(e);
    }
    sherlockMenuLoader =
    new SherlockMenuLoader(resourceExtractor, new AttrResourceLoader(resourceExtractor));
    }

    @Override protected void loadOtherResources(File resourceDir) {
    DocumentLoader loader = new DocumentLoader(sherlockMenuLoader);
    try {
    loader.loadResourceXmlDirs(resourceDir.listFiles(new FileFilter() {
    @Override public boolean accept(File file) {
    return file.getPath().contains(File.separator + "menu");
    }
    }));
    } catch (Exception e) {
    throw new RuntimeException(e);
    }
    }

    public void inflateSherlockMenu(Context context, int resource, Menu root) {
    sherlockMenuLoader.inflateMenu(context, resource, root);
    }
    }
    258 changes: 258 additions & 0 deletions TestSherlockMenuItem.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,258 @@
    package com.squareup.test.actionbarsherlock;

    import android.content.Intent;
    import android.graphics.drawable.Drawable;
    import android.view.ContextMenu;
    import android.view.View;
    import com.actionbarsherlock.view.ActionProvider;
    import com.actionbarsherlock.view.MenuItem;
    import com.actionbarsherlock.view.SubMenu;
    import com.xtremelabs.robolectric.Robolectric;

    /**
    * Copied from {@link com.xtremelabs.robolectric.tester.android.view.TestMenuItem}.
    */
    public class TestSherlockMenuItem implements MenuItem {

    private int itemId;
    private CharSequence title;
    private boolean enabled = true;
    private OnMenuItemClickListener menuItemClickListener;
    public int iconRes;
    private Intent intent;
    private SubMenu subMenu;

    public TestSherlockMenuItem() {
    super();
    }

    public TestSherlockMenuItem(int itemId) {
    super();
    this.itemId = itemId;
    }

    public void setItemId(int itemId) {
    this.itemId = itemId;
    }

    @Override
    public int getItemId() {
    return itemId;
    }

    @Override
    public int getGroupId() {
    return 0;
    }

    @Override
    public int getOrder() {
    return 0;
    }

    @Override
    public MenuItem setTitle(CharSequence title) {
    this.title = title;
    return this;
    }

    @Override
    public MenuItem setTitle(int title) {
    return null;
    }

    @Override
    public CharSequence getTitle() {
    return title;
    }

    @Override
    public MenuItem setTitleCondensed(CharSequence title) {
    return null;
    }

    @Override
    public CharSequence getTitleCondensed() {
    return null;
    }

    @Override
    public MenuItem setIcon(Drawable icon) {
    return null;
    }

    @Override
    public MenuItem setIcon(int iconRes) {
    this.iconRes = iconRes;
    return this;
    }

    @Override
    public Drawable getIcon() {
    return null;
    }

    @Override
    public MenuItem setIntent(Intent intent) {
    this.intent = intent;
    return this;
    }

    @Override
    public Intent getIntent() {
    return this.intent;
    }

    @Override
    public MenuItem setShortcut(char numericChar, char alphaChar) {
    return null;
    }

    @Override
    public MenuItem setNumericShortcut(char numericChar) {
    return null;
    }

    @Override
    public char getNumericShortcut() {
    return 0;
    }

    @Override
    public MenuItem setAlphabeticShortcut(char alphaChar) {
    return null;
    }

    @Override
    public char getAlphabeticShortcut() {
    return 0;
    }

    @Override
    public MenuItem setCheckable(boolean checkable) {
    return null;
    }

    @Override
    public boolean isCheckable() {
    return false;
    }

    @Override
    public MenuItem setChecked(boolean checked) {
    return null;
    }

    @Override
    public boolean isChecked() {
    return false;
    }

    @Override
    public MenuItem setVisible(boolean visible) {
    return null;
    }

    @Override
    public boolean isVisible() {
    return false;
    }

    @Override
    public MenuItem setEnabled(boolean enabled) {
    this.enabled = enabled;
    return this;
    }

    @Override
    public boolean isEnabled() {
    return enabled;
    }

    @Override
    public boolean hasSubMenu() {
    return subMenu != null;
    }

    @Override
    public SubMenu getSubMenu() {
    return subMenu;
    }

    public void setSubMenu(SubMenu subMenu) {
    this.subMenu = subMenu;
    }

    @Override
    public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) {
    this.menuItemClickListener = menuItemClickListener;
    return this;
    }

    @Override
    public ContextMenu.ContextMenuInfo getMenuInfo() {
    return null;
    }

    @Override
    public void setShowAsAction(int i) {
    }

    @Override
    public MenuItem setShowAsActionFlags(int i) {
    return null;
    }

    @Override
    public MenuItem setActionView(View view) {
    return null;
    }

    @Override
    public MenuItem setActionView(int i) {
    return null;
    }

    @Override
    public View getActionView() {
    return null;
    }

    @Override
    public MenuItem setActionProvider(ActionProvider actionProvider) {
    return null;
    }

    @Override
    public ActionProvider getActionProvider() {
    return null;
    }

    @Override
    public boolean expandActionView() {
    return false;
    }

    @Override
    public boolean collapseActionView() {
    return false;
    }

    @Override
    public boolean isActionViewExpanded() {
    return false;
    }

    @Override
    public MenuItem setOnActionExpandListener(OnActionExpandListener onActionExpandListener) {
    return null;
    }

    public void click() {
    if (enabled && menuItemClickListener != null) {
    menuItemClickListener.onMenuItemClick(this);
    } else if (enabled && intent != null) {
    Robolectric.application.startActivity(intent);
    }
    }
    }
  8. @JakeWharton JakeWharton created this gist Sep 29, 2012.
    52 changes: 52 additions & 0 deletions ActionBarSherlockRobolectric.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,52 @@
    // Copyright 2012 Square, Inc.
    package com.squareup.test;

    import android.app.Activity;
    import android.content.Context;
    import android.view.LayoutInflater;
    import android.view.View;
    import com.actionbarsherlock.ActionBarSherlock;
    import com.actionbarsherlock.app.ActionBar;
    import com.actionbarsherlock.internal.ActionBarSherlockCompat;
    import com.actionbarsherlock.internal.ActionBarSherlockNative;

    import static com.xtremelabs.robolectric.Robolectric.shadowOf;

    /**
    * During initialization, {@link ActionBarSherlock} figures out which {@link
    * com.actionbarsherlock.app.ActionBar} to use based on the API level. It does this by checking the
    * Build.Version.SDK_INT value which depends on the hidden <i>SystemProperties</i> class.
    *
    * Because Roboelectric does not have this, it always returns <code>0</code> for its API level
    * causing {@link ActionBarSherlock} to crash. This class helps resolve this issue by providing
    * an {@link ActionBarSherlockNative} implementation for API level 0.
    * @see ActionBarSherlock#registerImplementation(Class)
    */
    @ActionBarSherlock.Implementation(api = 0)
    public class ActionBarSherlockRobolectric extends ActionBarSherlockCompat {
    final private ActionBar actionBar;

    public ActionBarSherlockRobolectric(Activity activity, int flags) {
    super(activity, flags);
    actionBar = new MockActionBar(activity);
    }

    @Override public void setContentView(int layoutResId) {
    LayoutInflater layoutInflater = LayoutInflater.from(mActivity);
    View contentView = layoutInflater.inflate(layoutResId, null);

    shadowOf(mActivity).setContentView(contentView);
    }

    @Override public void setContentView(View view) {
    shadowOf(mActivity).setContentView(view);
    }

    @Override public ActionBar getActionBar() {
    return actionBar;
    }

    @Override protected Context getThemedContext() {
    return mActivity;
    }
    }
    57 changes: 57 additions & 0 deletions ActionBarSherlockTestRunner.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,57 @@
    // Copyright 2012 Square, Inc.
    package com.squareup.test;

    import com.squareup.test.actionbarsherlock.SherlockResourceLoader;
    import com.xtremelabs.robolectric.RobolectricConfig;
    import com.xtremelabs.robolectric.RobolectricTestRunner;
    import com.xtremelabs.robolectric.bytecode.RobolectricClassLoader;
    import com.xtremelabs.robolectric.bytecode.ShadowWrangler;
    import com.xtremelabs.robolectric.res.ResourceLoader;
    import java.util.Arrays;
    import org.junit.runners.model.InitializationError;

    /**
    * {@link RobolectricTestRunner} primarily used to provide a custom {@link
    * RobolectricClassLoader} for instrumenting our own packages. It also provides a
    * custom {@link ResourceLoader} to load ActionBarSherlock menus.
    */
    public class ActionBarSherlockTestRunner extends RobolectricTestRunner {

    private static RobolectricClassLoader classLoader;

    protected ActionBarSherlockTestRunner(Class<?> testClass, RobolectricConfig robolectricConfig)
    throws InitializationError {
    super(testClass, ShadowWrangler.getInstance(), getClassLoader(), robolectricConfig);
    }

    /** Use this to add more packages that you want to be instrumented by Robolectric. */
    private static RobolectricClassLoader getClassLoader() {
    // Please don't cry.
    if (classLoader == null && !isInstrumented()) {
    classLoader = new RobolectricClassLoader(ShadowWrangler.getInstance(),
    Arrays.asList("com.actionbarsherlock.app", "com.actionbarsherlock.view"));
    }
    return classLoader;
    }

    /** Return a {@link SherlockResourceLoader} instead, which loads ABS menus for testing. */
    @Override protected ResourceLoader createResourceLoader(RobolectricConfig robolectricConfig) {
    ResourceLoader resourceLoader = resourceLoaderForRootAndDirectory.get(robolectricConfig);
    if (resourceLoader == null) {
    try {
    robolectricConfig.validate();

    String rClassName = robolectricConfig.getRClassName();
    Class rClass = Class.forName(rClassName);
    resourceLoader = new SherlockResourceLoader(robolectricConfig.getRealSdkVersion(), rClass,
    robolectricConfig.getResourcePath(), robolectricConfig.getAssetsDirectory());
    resourceLoaderForRootAndDirectory.put(robolectricConfig, resourceLoader);
    } catch (Exception e) {
    throw new RuntimeException(e);
    }
    }

    resourceLoader.setStrictI18n(robolectricConfig.getStrictI18n());
    return resourceLoader;
    }
    }
    181 changes: 181 additions & 0 deletions MockActionBar.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,181 @@
    // Copyright 2012 Square, Inc.
    package com.squareup.test;

    import android.content.Context;
    import android.graphics.drawable.Drawable;
    import android.view.View;
    import android.widget.SpinnerAdapter;
    import com.actionbarsherlock.app.ActionBar;

    public class MockActionBar extends ActionBar {
    String title;
    String subtitle;
    View customView;
    Context realContext;

    public MockActionBar(Context context) {
    realContext = context;
    }

    @Override public void setCustomView(View view) {
    setCustomView(view, null);
    }

    @Override public void setCustomView(View view, LayoutParams layoutParams) {
    this.customView = view;
    }

    @Override public void setCustomView(int resId) {
    }

    @Override public void setIcon(int resId) {
    }

    @Override public void setIcon(Drawable icon) {
    }

    @Override public void setLogo(int resId) {
    }

    @Override public void setLogo(Drawable logo) {
    }

    @Override
    public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) {
    }

    @Override public void setSelectedNavigationItem(int position) {
    }

    @Override public int getSelectedNavigationIndex() {
    return 0;
    }

    @Override public int getNavigationItemCount() {
    return 0;
    }

    @Override public void setTitle(CharSequence title) {
    this.title = (String) title;
    }

    @Override public void setTitle(int resId) {
    title = realContext.getString(resId);
    }

    @Override public void setSubtitle(CharSequence newSubtitle) {
    subtitle = (String) newSubtitle;
    }

    @Override public void setSubtitle(int resId) {
    subtitle = realContext.getString(resId);
    }

    @Override public void setDisplayOptions(int options) {
    }

    @Override public void setDisplayOptions(int options, int mask) {
    }

    @Override public void setDisplayUseLogoEnabled(boolean useLogo) {
    }

    @Override public void setDisplayShowHomeEnabled(boolean showHome) {
    }

    @Override public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) {
    }

    @Override public void setDisplayShowTitleEnabled(boolean showTitle) {
    }

    @Override public void setDisplayShowCustomEnabled(boolean showCustom) {
    }

    @Override public void setBackgroundDrawable(Drawable d) {
    }

    @Override public View getCustomView() {
    return customView;
    }

    @Override public CharSequence getTitle() {
    return title;
    }

    @Override public CharSequence getSubtitle() {
    return subtitle;
    }

    @Override public int getNavigationMode() {
    return 0;
    }

    @Override public void setNavigationMode(int mode) {
    }

    @Override public int getDisplayOptions() {
    return 0;
    }

    @Override public Tab newTab() {
    return null;
    }

    @Override public void addTab(Tab tab) {
    }

    @Override public void addTab(Tab tab, boolean setSelected) {
    }

    @Override public void addTab(Tab tab, int position) {
    }

    @Override public void addTab(Tab tab, int position, boolean setSelected) {
    }

    @Override public void removeTab(Tab tab) {
    }

    @Override public void removeTabAt(int position) {
    }

    @Override public void removeAllTabs() {
    }

    @Override public void selectTab(Tab tab) {
    }

    @Override public Tab getSelectedTab() {
    return null;
    }

    @Override public Tab getTabAt(int index) {
    return null;
    }

    @Override public int getTabCount() {
    return 0;
    }

    @Override public int getHeight() {
    return 0;
    }

    @Override public void show() {
    }

    @Override public void hide() {
    }

    @Override public boolean isShowing() {
    return false;
    }

    @Override public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
    }

    @Override
    public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
    }
    }
    9 changes: 9 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,9 @@
    You need to do this once in your base test runner:

    ```java
    ActionBarSherlock.registerImplementation(ActionBarSherlockRobolectric.class);
    ```

    and your Robolectric should have this patch:

    https://github.com/square/robolectric/commit/fac24e5ef6d5830e6e67ad27aff2f8f57cedb5a1